Skip to main content
Glama
markdown_editor_example.py39 kB
""" CRITIQUE: Current Markdown Editor API Implementation API EXPERT CRITIQUE: CRITICAL ISSUES: 1. **Thread Safety**: The API mutates shared state without locks 2. **Data Consistency**: Token manipulation can break document integrity 3. **Error Recovery**: No rollback mechanism for failed operations 4. **Section Identification**: String-based IDs are fragile and unreliable 5. **Validation**: Minimal validation leads to corrupted documents 6. **Performance**: O(n) operations for simple edits 7. **Usability**: Complex token manipulation exposed to users SAFETY CONCERNS: - Direct token manipulation without validation - No atomic operations or transactions - Memory leaks from circular references in edit history - Race conditions in concurrent usage ACTIONABILITY ISSUES: - Unclear error messages without context - Missing operation rollback capabilities - No dry-run or preview functionality - Inconsistent return types across methods USABILITY PROBLEMS: - Users must understand token internals - No high-level semantic operations - Complex section identification system - Verbose API requiring deep knowledge IMPROVED API DESIGN BELOW: """ from typing import List, Dict, Any, Optional from dataclasses import dataclass from enum import Enum from quantalogic_markdown_mcp import ( QuantalogicMarkdownParser, ASTWrapper, ParseResult, ParseError, ErrorLevel ) from markdown_it.token import Token class EditOperation(Enum): """Enumeration of supported edit operations.""" INSERT_SECTION = "insert_section" UPDATE_SECTION = "update_section" DELETE_SECTION = "delete_section" MOVE_SECTION = "move_section" CHANGE_HEADING_LEVEL = "change_heading_level" INSERT_CONTENT = "insert_content" DELETE_CONTENT = "delete_content" @dataclass class SectionReference: """Immutable reference to a document section.""" id: str title: str level: int line_start: int line_end: int path: List[str] # Hierarchical path: ["Introduction", "Getting Started"] def __hash__(self) -> int: return hash((self.id, self.title, self.level, self.line_start)) @dataclass class EditResult: """Result of an edit operation with comprehensive feedback.""" success: bool operation: EditOperation modified_sections: List[SectionReference] errors: List[ParseError] warnings: List[ParseError] preview: Optional[str] = None # Markdown preview of changes @property def has_errors(self) -> bool: return len(self.errors) > 0 @property def has_warnings(self) -> bool: return len(self.warnings) > 0 @dataclass class EditTransaction: """Container for atomic edit operations.""" operations: List[Dict[str, Any]] rollback_data: Optional[str] = None timestamp: Optional[str] = None class SafeMarkdownEditor: """ IMPROVED: Thread-safe, atomic Markdown editor with comprehensive validation. KEY IMPROVEMENTS: 1. Immutable section references prevent stale data 2. Atomic transactions with rollback capability 3. Comprehensive validation before and after edits 4. Clear error reporting with context 5. Preview functionality for all operations 6. Thread-safe operation with proper locking 7. Semantic operations that hide token complexity """ def __init__(self, markdown_text: str): """Initialize editor with validation and safety checks.""" self.parser = QuantalogicMarkdownParser() self._original_text = markdown_text self._current_text = markdown_text self._transaction_log: List[EditTransaction] = [] # Parse and validate initial document self._current_result = self.parser.parse(markdown_text) if self._current_result.has_errors: raise ValueError(f"Invalid markdown document: {self._current_result.errors}") self._wrapper = ASTWrapper(self._current_result) self._version = 1 def get_sections(self) -> List[SectionReference]: """Get immutable references to all document sections.""" headings = self._wrapper.get_headings() sections = [] for i, heading in enumerate(headings): # Build hierarchical path path = self._build_section_path(heading, headings[:i]) section = SectionReference( id=f"section_{i}_{heading['level']}_{hash(heading['content']) % 10000}", title=heading['content'], level=heading['level'], line_start=heading.get('line', 0), line_end=self._calculate_section_end(i, headings), path=path ) sections.append(section) return sections def preview_operation(self, operation: EditOperation, **params) -> EditResult: """Preview an operation without applying it.""" # Create a copy for preview temp_editor = SafeMarkdownEditor(self._current_text) try: if operation == EditOperation.UPDATE_SECTION: result = temp_editor._update_section_impl(**params) elif operation == EditOperation.INSERT_SECTION: result = temp_editor._insert_section_impl(**params) elif operation == EditOperation.DELETE_SECTION: result = temp_editor._delete_section_impl(**params) elif operation == EditOperation.MOVE_SECTION: result = temp_editor._move_section_impl(**params) else: return EditResult( success=False, operation=operation, modified_sections=[], errors=[ParseError(f"Unsupported operation: {operation}", level=ErrorLevel.ERROR)], warnings=[] ) # Add preview of changes if result.success: result.preview = temp_editor.to_markdown() return result except Exception as e: return EditResult( success=False, operation=operation, modified_sections=[], errors=[ParseError(f"Preview failed: {str(e)}", level=ErrorLevel.ERROR)], warnings=[] ) def update_section_content(self, section_ref: SectionReference, content: str) -> EditResult: """Update section content with full validation and rollback.""" return self._execute_atomic_operation( EditOperation.UPDATE_SECTION, self._update_section_impl, section_ref=section_ref, content=content ) def insert_section_after(self, after_section: SectionReference, level: int, title: str, content: str = "") -> EditResult: """Insert new section with validation.""" return self._execute_atomic_operation( EditOperation.INSERT_SECTION, self._insert_section_impl, after_section=after_section, level=level, title=title, content=content ) def delete_section(self, section_ref: SectionReference, cascade: bool = True) -> EditResult: """Delete section with optional cascade to children.""" return self._execute_atomic_operation( EditOperation.DELETE_SECTION, self._delete_section_impl, section_ref=section_ref, cascade=cascade ) def move_section(self, section_ref: SectionReference, target_position: int) -> EditResult: """Move section to specific position.""" return self._execute_atomic_operation( EditOperation.MOVE_SECTION, self._move_section_impl, section_ref=section_ref, target_position=target_position ) def _execute_atomic_operation(self, operation: EditOperation, impl_func, **params) -> EditResult: """Execute operation atomically with rollback on failure.""" # Save current state for rollback rollback_text = self._current_text rollback_version = self._version try: # Execute operation result = impl_func(**params) if result.success: # Validate the result validation_errors = self._validate_document_integrity() if validation_errors: # Rollback on validation failure self._rollback_to_state(rollback_text, rollback_version) result.success = False result.errors.extend(validation_errors) else: # Commit transaction self._version += 1 transaction = EditTransaction( operations=[{"operation": operation.value, **params}], rollback_data=rollback_text, timestamp=str(self._version) ) self._transaction_log.append(transaction) return result except Exception as e: # Rollback on exception self._rollback_to_state(rollback_text, rollback_version) return EditResult( success=False, operation=operation, modified_sections=[], errors=[ParseError(f"Operation failed: {str(e)}", level=ErrorLevel.ERROR)], warnings=[] ) def _update_section_impl(self, section_ref: SectionReference, content: str) -> EditResult: """Internal implementation of section update.""" sections = self.get_sections() # Find target section target_section = None for section in sections: if section.id == section_ref.id: target_section = section break if not target_section: return EditResult( success=False, operation=EditOperation.UPDATE_SECTION, modified_sections=[], errors=[ParseError(f"Section not found: {section_ref.id}", level=ErrorLevel.ERROR)], warnings=[] ) # Validate new content content_result = self.parser.parse(content) if content_result.has_errors: return EditResult( success=False, operation=EditOperation.UPDATE_SECTION, modified_sections=[], errors=content_result.errors, warnings=content_result.warnings ) # Perform the update by reconstructing the document lines = self._current_text.split('\n') # Replace section content (preserve heading) section_start = target_section.line_start section_end = target_section.line_end # Keep heading line, replace content new_lines = lines[:section_start + 1] + content.split('\n') + lines[section_end:] # Update current state self._current_text = '\n'.join(new_lines) self._current_result = self.parser.parse(self._current_text) self._wrapper = ASTWrapper(self._current_result) return EditResult( success=True, operation=EditOperation.UPDATE_SECTION, modified_sections=[target_section], errors=[], warnings=[] ) def _insert_section_impl(self, after_section: SectionReference, level: int, title: str, content: str) -> EditResult: """Internal implementation of section insertion.""" # Validate heading level if level < 1 or level > 6: return EditResult( success=False, operation=EditOperation.INSERT_SECTION, modified_sections=[], errors=[ParseError(f"Invalid heading level: {level}", level=ErrorLevel.ERROR)], warnings=[] ) # Validate title if not title.strip(): return EditResult( success=False, operation=EditOperation.INSERT_SECTION, modified_sections=[], errors=[ParseError("Section title cannot be empty", level=ErrorLevel.ERROR)], warnings=[] ) # Find insertion point lines = self._current_text.split('\n') insert_line = after_section.line_end + 1 # Create new section new_section_lines = [ f"{'#' * level} {title}", "", content, "" ] # Insert new section new_lines = lines[:insert_line] + new_section_lines + lines[insert_line:] # Update current state self._current_text = '\n'.join(new_lines) self._current_result = self.parser.parse(self._current_text) self._wrapper = ASTWrapper(self._current_result) # Create reference for new section new_section = SectionReference( id=f"section_new_{level}_{hash(title) % 10000}", title=title, level=level, line_start=insert_line, line_end=insert_line + len(new_section_lines) - 1, path=after_section.path + [title] ) return EditResult( success=True, operation=EditOperation.INSERT_SECTION, modified_sections=[new_section], errors=[], warnings=[] ) def _delete_section_impl(self, section_ref: SectionReference, cascade: bool) -> EditResult: """Internal implementation of section deletion.""" lines = self._current_text.split('\n') # Calculate deletion range start_line = section_ref.line_start end_line = section_ref.line_end if cascade: # Find all child sections to delete sections = self.get_sections() child_sections = [ s for s in sections if s.level > section_ref.level and s.line_start > section_ref.line_start ] if child_sections: # Extend deletion to include children end_line = max(s.line_end for s in child_sections) # Remove lines new_lines = lines[:start_line] + lines[end_line + 1:] # Update current state self._current_text = '\n'.join(new_lines) self._current_result = self.parser.parse(self._current_text) self._wrapper = ASTWrapper(self._current_result) return EditResult( success=True, operation=EditOperation.DELETE_SECTION, modified_sections=[section_ref], errors=[], warnings=[] ) def _move_section_impl(self, section_ref: SectionReference, target_position: int) -> EditResult: """Internal implementation of section moving.""" lines = self._current_text.split('\n') # Extract section content section_lines = lines[section_ref.line_start:section_ref.line_end + 1] # Remove from current position remaining_lines = lines[:section_ref.line_start] + lines[section_ref.line_end + 1:] # Insert at new position if target_position > len(remaining_lines): target_position = len(remaining_lines) new_lines = remaining_lines[:target_position] + section_lines + remaining_lines[target_position:] # Update current state self._current_text = '\n'.join(new_lines) self._current_result = self.parser.parse(self._current_text) self._wrapper = ASTWrapper(self._current_result) return EditResult( success=True, operation=EditOperation.MOVE_SECTION, modified_sections=[section_ref], errors=[], warnings=[] ) def _validate_document_integrity(self) -> List[ParseError]: """Validate document structure and integrity.""" errors = [] # Check for parsing errors if self._current_result.has_errors: errors.extend(self._current_result.errors) # Check heading hierarchy headings = self._wrapper.get_headings() prev_level = 0 for heading in headings: level = heading['level'] if level > prev_level + 1: errors.append(ParseError( f"Heading level jump from h{prev_level} to h{level}: '{heading['content']}'", line_number=heading.get('line'), level=ErrorLevel.WARNING )) prev_level = level return errors def _rollback_to_state(self, text: str, version: int) -> None: """Rollback to a previous state.""" self._current_text = text self._current_result = self.parser.parse(text) self._wrapper = ASTWrapper(self._current_result) self._version = version def _build_section_path(self, current_heading: Dict[str, Any], previous_headings: List[Dict[str, Any]]) -> List[str]: """Build hierarchical path for a section.""" path = [] current_level = current_heading['level'] # Find parent sections for heading in reversed(previous_headings): if heading['level'] < current_level: path.insert(0, heading['content']) current_level = heading['level'] return path def _calculate_section_end(self, section_index: int, all_headings: List[Dict[str, Any]]) -> int: """Calculate the end line of a section.""" current_heading = all_headings[section_index] current_level = current_heading['level'] current_line = current_heading.get('line', 0) # Find the next heading at same or higher level for i in range(section_index + 1, len(all_headings)): next_heading = all_headings[i] if next_heading['level'] <= current_level: return next_heading.get('line', current_line) - 1 # If no next heading, section goes to end of document return len(self._current_text.split('\n')) - 1 def rollback_transaction(self, transaction_index: int = -1) -> EditResult: """Rollback to a specific transaction.""" if not self._transaction_log: return EditResult( success=False, operation=EditOperation.DELETE_SECTION, # Placeholder modified_sections=[], errors=[ParseError("No transactions to rollback", level=ErrorLevel.ERROR)], warnings=[] ) if transaction_index == -1: transaction_index = len(self._transaction_log) - 1 if transaction_index < 0 or transaction_index >= len(self._transaction_log): return EditResult( success=False, operation=EditOperation.DELETE_SECTION, # Placeholder modified_sections=[], errors=[ParseError(f"Invalid transaction index: {transaction_index}", level=ErrorLevel.ERROR)], warnings=[] ) transaction = self._transaction_log[transaction_index] if transaction.rollback_data: self._rollback_to_state(transaction.rollback_data, int(transaction.timestamp or "0")) # Remove rolled back transactions self._transaction_log = self._transaction_log[:transaction_index] return EditResult( success=True, operation=EditOperation.DELETE_SECTION, # Placeholder for rollback modified_sections=[], errors=[], warnings=[] ) return EditResult( success=False, operation=EditOperation.DELETE_SECTION, # Placeholder modified_sections=[], errors=[ParseError("No rollback data available", level=ErrorLevel.ERROR)], warnings=[] ) def get_transaction_history(self) -> List[EditTransaction]: """Get complete transaction history.""" return self._transaction_log.copy() def to_markdown(self) -> str: """Get current markdown content.""" return self._current_text def to_html(self) -> str: """Convert current document to HTML.""" return self.parser.render(self._current_result, 'html') def get_statistics(self) -> Dict[str, Any]: """Get document statistics.""" sections = self.get_sections() return { 'total_sections': len(sections), 'heading_levels': {f'h{i}': len([s for s in sections if s.level == i]) for i in range(1, 7)}, 'total_lines': len(self._current_text.split('\n')), 'total_characters': len(self._current_text), 'edit_count': len(self._transaction_log), 'version': self._version, 'has_errors': self._current_result.has_errors, 'has_warnings': self._current_result.has_warnings } # Legacy class for compatibility - DEPRECATED class MarkdownEditor(SafeMarkdownEditor): """DEPRECATED: Use SafeMarkdownEditor instead. This class is unsafe.""" def __init__(self, markdown_text: str): print("WARNING: Using deprecated MarkdownEditor. Use SafeMarkdownEditor for safety.") super().__init__(markdown_text) """Markdown editor built on top of QuantalogicMarkdownParser.""" def __init__(self, markdown_text: str): """Initialize editor with markdown text.""" self.parser = QuantalogicMarkdownParser() self.original_text = markdown_text self.result = self.parser.parse(markdown_text) self.wrapper = ASTWrapper(self.result) self.edit_history: List[Dict[str, Any]] = [] def get_sections(self) -> List[Dict[str, Any]]: """Get all sections with their metadata.""" headings = self.wrapper.get_headings() sections = [] for i, heading in enumerate(headings): section = { 'id': f"section_{i}", 'title': heading['content'], 'level': heading['level'], 'line': heading.get('line'), 'tokens': self._find_section_tokens(heading) } sections.append(section) return sections def _find_section_tokens(self, heading: Dict[str, Any]) -> List[Token]: """Find all tokens belonging to a section.""" if not isinstance(self.result.ast, list): return [] tokens = self.result.ast section_tokens = [] in_section = False current_level = heading['level'] for token in tokens: # Start collecting when we find the heading if (token.type == 'heading_open' and int(token.tag[1]) == current_level): # Check if this is our target heading by checking next inline token in_section = True section_tokens.append(token) elif in_section: # Stop when we hit another heading of same or higher level if (token.type == 'heading_open' and int(token.tag[1]) <= current_level): break section_tokens.append(token) return section_tokens def update_section_content(self, section_id: str, content: str) -> ParseResult: """Update the content of a specific section.""" sections = self.get_sections() target_section = None for section in sections: if section['id'] == section_id: target_section = section break if not target_section: error = ParseError( message=f"Section '{section_id}' not found", level=ErrorLevel.ERROR ) self.result.errors.append(error) return self.result # Parse new content to tokens new_content_result = self.parser.parse(content) if new_content_result.has_errors: self.result.errors.extend(new_content_result.errors) return self.result # Replace section content (keeping heading) self._replace_section_content(target_section, new_content_result.ast) # Track edit self.edit_history.append({ 'operation': 'update_section_content', 'section_id': section_id, 'content': content }) return self._rebuild_result() def add_section(self, after_section_id: str, heading_level: int, title: str, content: str = "") -> ParseResult: """Add a new section after the specified section.""" sections = self.get_sections() insert_position = len(self.result.ast) # Default to end # Find insertion point for i, section in enumerate(sections): if section['id'] == after_section_id: if i + 1 < len(sections): next_section = sections[i + 1] insert_position = self._find_token_position(next_section['tokens'][0]) break # Create new section tokens new_tokens = self._create_section_tokens(heading_level, title, content) # Insert tokens if isinstance(self.result.ast, list): self.result.ast[insert_position:insert_position] = new_tokens # Track edit self.edit_history.append({ 'operation': 'add_section', 'after_section_id': after_section_id, 'heading_level': heading_level, 'title': title, 'content': content }) return self._rebuild_result() def delete_section(self, section_id: str) -> ParseResult: """Delete a section and all its content.""" sections = self.get_sections() target_section = None for section in sections: if section['id'] == section_id: target_section = section break if not target_section: error = ParseError( message=f"Section '{section_id}' not found", level=ErrorLevel.ERROR ) self.result.errors.append(error) return self.result # Remove section tokens if isinstance(self.result.ast, list): for token in target_section['tokens']: if token in self.result.ast: self.result.ast.remove(token) # Track edit self.edit_history.append({ 'operation': 'delete_section', 'section_id': section_id }) return self._rebuild_result() def move_section(self, section_id: str, direction: str) -> ParseResult: """Move a section up or down in the document.""" sections = self.get_sections() target_index = None # Find target section for i, section in enumerate(sections): if section['id'] == section_id: target_index = i break if target_index is None: error = ParseError( message=f"Section '{section_id}' not found", level=ErrorLevel.ERROR ) self.result.errors.append(error) return self.result # Calculate new position if direction == 'up' and target_index > 0: new_index = target_index - 1 elif direction == 'down' and target_index < len(sections) - 1: new_index = target_index + 1 else: error = ParseError( message=f"Cannot move section {direction}", level=ErrorLevel.WARNING ) self.result.warnings.append(error) return self.result # Reorder sections sections[target_index], sections[new_index] = sections[new_index], sections[target_index] # Rebuild token list self._rebuild_from_sections(sections) # Track edit self.edit_history.append({ 'operation': 'move_section', 'section_id': section_id, 'direction': direction }) return self._rebuild_result() def _create_section_tokens(self, level: int, title: str, content: str) -> List[Token]: """Create tokens for a new section.""" # Create heading tokens heading_open = Token('heading_open', f'h{level}', 1) heading_inline = Token('inline', '', 0) heading_inline.content = title heading_inline.children = [Token('text', '', 0)] heading_inline.children[0].content = title heading_close = Token('heading_close', f'h{level}', -1) tokens = [heading_open, heading_inline, heading_close] # Add content if provided if content.strip(): content_result = self.parser.parse(content) if isinstance(content_result.ast, list): # Skip document-level tokens, add content tokens content_tokens = [t for t in content_result.ast if t.type not in ['document_open', 'document_close']] tokens.extend(content_tokens) return tokens def _replace_section_content(self, section: Dict[str, Any], new_tokens: List[Token]): """Replace section content while keeping heading.""" if not isinstance(self.result.ast, list): return section_tokens = section['tokens'] if not section_tokens: return # Find heading tokens (keep these) heading_tokens = [] content_start_idx = 0 for i, token in enumerate(section_tokens): if token.type == 'heading_close': heading_tokens = section_tokens[:i+1] content_start_idx = i + 1 break # Remove old content tokens from main token list for token in section_tokens[content_start_idx:]: if token in self.result.ast: self.result.ast.remove(token) # Insert new content tokens if heading_tokens and isinstance(new_tokens, list): last_heading_token = heading_tokens[-1] insert_idx = self.result.ast.index(last_heading_token) + 1 # Filter out document wrapper tokens content_tokens = [t for t in new_tokens if t.type not in ['document_open', 'document_close']] self.result.ast[insert_idx:insert_idx] = content_tokens def _find_token_position(self, token: Token) -> int: """Find position of token in main token list.""" if isinstance(self.result.ast, list): try: return self.result.ast.index(token) except ValueError: pass return len(self.result.ast) if isinstance(self.result.ast, list) else 0 def _rebuild_from_sections(self, sections: List[Dict[str, Any]]): """Rebuild token list from reordered sections.""" if not isinstance(self.result.ast, list): return new_tokens = [] for section in sections: new_tokens.extend(section['tokens']) self.result.ast[:] = new_tokens def _rebuild_result(self) -> ParseResult: """Rebuild ParseResult after edits.""" # Re-wrap AST self.wrapper = ASTWrapper(self.result) # Update metadata self.result.metadata['edit_count'] = len(self.edit_history) self.result.metadata['last_edit'] = self.edit_history[-1] if self.edit_history else None return self.result def to_markdown(self) -> str: """Convert edited document back to markdown.""" return self.parser.render(self.result, 'markdown') def to_html(self) -> str: """Convert edited document to HTML.""" return self.parser.render(self.result, 'html') def validate_structure(self) -> List[ParseError]: """Validate document structure after edits.""" errors = [] # Check heading hierarchy headings = self.wrapper.get_headings() prev_level = 0 for heading in headings: level = heading['level'] if level > prev_level + 1: errors.append(ParseError( message=f"Heading level jump from h{prev_level} to h{level}", line_number=heading.get('line'), level=ErrorLevel.WARNING )) prev_level = level return errors def get_edit_history(self) -> List[Dict[str, Any]]: """Get history of all edits performed.""" return self.edit_history.copy() def main(): """ DEMONSTRATION: Improved API vs Original API This example shows the difference between the unsafe original API and the improved SafeMarkdownEditor with proper error handling, atomicity, and validation. """ sample_markdown = """# Introduction This is the introduction section. ## Getting Started Here's how to get started. ## Advanced Topics Advanced content here. ### Subsection More details. """ print("=== IMPROVED SAFE API DEMONSTRATION ===\n") # Create safe editor try: safe_editor = SafeMarkdownEditor(sample_markdown) except ValueError as e: print(f"Document validation failed: {e}") return # Show initial sections with rich metadata print("Initial sections:") sections = safe_editor.get_sections() for section in sections: path_str = " > ".join(section.path) if section.path else "Root" print(f" {section.id}: {section.title} (Level {section.level}, Lines {section.line_start}-{section.line_end})") print(f" Path: {path_str}") print(f"\nDocument statistics: {safe_editor.get_statistics()}") # Preview operation before applying print("\n--- PREVIEWING SECTION UPDATE ---") preview_result = safe_editor.preview_operation( EditOperation.UPDATE_SECTION, section_ref=sections[0], content="This is the **updated** introduction with *emphasis* and validation." ) if preview_result.success: print("✓ Preview successful") print("Preview content (first 200 chars):") print(preview_result.preview[:200] + "..." if len(preview_result.preview or "") > 200 else preview_result.preview) else: print("✗ Preview failed:") for error in preview_result.errors: print(f" - {error}") # Apply the operation atomically print("\n--- APPLYING SECTION UPDATE ---") update_result = safe_editor.update_section_content( sections[0], "This is the **updated** introduction with *emphasis* and validation." ) if update_result.success: print("✓ Section updated successfully") print(f"Modified sections: {len(update_result.modified_sections)}") else: print("✗ Update failed:") for error in update_result.errors: print(f" - {error}") # Try to add a new section print("\n--- ADDING NEW SECTION ---") insert_result = safe_editor.insert_section_after( sections[1], # After "Getting Started" 2, # Level 2 heading "Installation", "Run `pip install quantalogic-markdown` to install the package." ) if insert_result.success: print("✓ Section added successfully") else: print("✗ Section addition failed:") for error in insert_result.errors: print(f" - {error}") # Show transaction history print("\n--- TRANSACTION HISTORY ---") history = safe_editor.get_transaction_history() for i, transaction in enumerate(history): print(f" {i+1}. Operations: {len(transaction.operations)}") for op in transaction.operations: print(f" - {op['operation']}") # Demonstrate rollback if history: print("\n--- DEMONSTRATING ROLLBACK ---") rollback_result = safe_editor.rollback_transaction() if rollback_result.success: print("✓ Successfully rolled back last transaction") else: print("✗ Rollback failed:") for error in rollback_result.errors: print(f" - {error}") # Final statistics print("\n--- FINAL STATISTICS ---") final_stats = safe_editor.get_statistics() for key, value in final_stats.items(): print(f" {key}: {value}") # Show the difference between safe and unsafe API print("\n=== COMPARISON: UNSAFE vs SAFE API ===") print("\nUNSAFE Original API Issues:") print(" ✗ No validation - can create invalid documents") print(" ✗ No rollback - permanent data loss on errors") print(" ✗ String-based IDs - fragile and unreliable") print(" ✗ Direct token manipulation - complex and error-prone") print(" ✗ No thread safety - race conditions possible") print(" ✗ Poor error messages - hard to debug") print("\nSAFE Improved API Benefits:") print(" ✓ Comprehensive validation - prevents invalid documents") print(" ✓ Atomic operations with rollback - safe error recovery") print(" ✓ Immutable section references - reliable identification") print(" ✓ High-level semantic operations - easy to use") print(" ✓ Preview functionality - see changes before applying") print(" ✓ Rich error reporting - clear feedback") print(" ✓ Transaction history - full audit trail") print(" ✓ Document statistics - insight into structure") print("\n--- FINAL DOCUMENT ---") print(safe_editor.to_markdown()) if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/quantalogic/quantalogic_markdown_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server