""" CLI Entry Point and Basic Commands - Issue #12 This module provides the command-line interface for MarkiTect, allowing users to interact with core functionality through terminal commands. Commands: - ingest: Process and store a markdown file - status: Show processing status and metadata for a file - list: List all stored files and their status Integration with existing components: - Uses DatabaseManager for file storage and retrieval - Uses DocumentManager for high-performance document processing - Maintains performance caching architecture """ import click import os import sys from pathlib import Path from typing import Optional from .database import DatabaseManager from .document_manager import DocumentManager # Global options for CLI configuration pass_config = click.make_pass_decorator(dict, ensure=True) @click.group() @click.option('--verbose', '-v', is_flag=True, help='Enable verbose output') @click.option('--config', 'config_file', type=click.Path(exists=True), help='Configuration file path') @click.option('--database', type=click.Path(), help='Database file path') @pass_config def cli(config, verbose, database, config_file): """ MarkiTect - Advanced Markdown engine for structured content. Process markdown files with front matter support, AST caching, and relational metadata queries. Examples: markitect ingest document.md # Process a markdown file markitect status document.md # Check file status markitect list # List all stored files """ # Store configuration in context config['verbose'] = verbose config['config_file'] = config_file # Determine database path if database: config['database_path'] = database else: # Default database location config['database_path'] = os.path.expanduser('~/.markitect/markitect.db') # Initialize database manager and ensure database exists try: db_manager = DatabaseManager(config['database_path']) db_manager.initialize_database() config['db_manager'] = db_manager if verbose: click.echo(f"Using database: {config['database_path']}", err=True) except Exception as e: click.echo(f"Error initializing database: {e}", err=True) sys.exit(1) @cli.command() @click.argument('file_path', type=click.Path(exists=True)) @pass_config def ingest(config, file_path): """ Process and store a markdown file. Ingests a markdown file into the MarkiTect system, parsing its content, extracting front matter, generating AST cache, and storing metadata in the database. FILE_PATH: Path to the markdown file to process Examples: markitect ingest README.md markitect ingest docs/guide.md """ try: file_path = Path(file_path) if config['verbose']: click.echo(f"Processing file: {file_path}") # Initialize document manager with database manager doc_manager = DocumentManager(config['db_manager']) # Ingest the file result = doc_manager.ingest_file(file_path) if config['verbose']: click.echo(f"Processing results:") click.echo(f" File: {result['metadata']['filename']}") click.echo(f" AST nodes: {len(result['ast'])} nodes") click.echo(f" Cache file: {result['ast_cache_path']}") click.echo(f" Parse time: {result['parse_time']:.2f}s") click.echo(f" Cache time: {result['cache_time']:.2f}s") click.echo(f"✓ Successfully ingested: {file_path.name}") except FileNotFoundError: click.echo(f"Error: File not found: {file_path}", err=True) sys.exit(1) except PermissionError: click.echo(f"Error: Permission denied accessing: {file_path}", err=True) sys.exit(1) except Exception as e: click.echo(f"Error processing file: {e}", err=True) if config['verbose']: import traceback click.echo(traceback.format_exc(), err=True) sys.exit(1) @cli.command() @click.argument('file_path', type=str) @pass_config def status(config, file_path): """ Show processing status and metadata for a file. Displays information about a file's processing status, metadata, and front matter content from the database. FILE_PATH: Path or name of the file to check Examples: markitect status README.md markitect status docs/guide.md """ try: if config['verbose']: click.echo(f"Checking status for: {file_path}") # Get file information from database db_manager = config['db_manager'] file_info = db_manager.get_markdown_file(file_path) if file_info: click.echo(f"File: {file_info['filename']}") click.echo(f"Status: Processed") click.echo(f"Created: {file_info['created_at']}") if file_info['front_matter']: try: front_matter = eval(file_info['front_matter']) # Safe for our controlled data if front_matter: click.echo("Front Matter:") for key, value in front_matter.items(): click.echo(f" {key}: {value}") except (ValueError, TypeError, SyntaxError): click.echo("Front Matter: (parsing error)") elif file_info['front_matter'] is None: pass # No front matter to display if config['verbose']: content_preview = file_info['content'][:200] + "..." if len(file_info['content']) > 200 else file_info['content'] click.echo(f"Content preview: {content_preview}") else: click.echo(f"File not found in database: {file_path}") click.echo("Use 'markitect ingest' to process the file first.") sys.exit(1) except Exception as e: click.echo(f"Error checking file status: {e}", err=True) if config['verbose']: import traceback click.echo(traceback.format_exc(), err=True) sys.exit(1) @cli.command() @pass_config def list(config): """ List all stored files and their status. Shows all markdown files that have been processed and stored in the MarkiTect database with their basic metadata. Examples: markitect list markitect --verbose list # Show detailed information """ try: if config['verbose']: click.echo("Retrieving all stored files...") db_manager = config['db_manager'] files = db_manager.list_markdown_files() if not files: click.echo("No files found in database.") click.echo("Use 'markitect ingest ' to add files.") return click.echo(f"Found {len(files)} file(s):") click.echo() for file_info in files: click.echo(f"📄 {file_info['filename']}") if config['verbose']: click.echo(f" Created: {file_info['created_at']}") if file_info.get('front_matter'): try: front_matter = eval(file_info['front_matter']) if front_matter: click.echo(f" Front matter: {list(front_matter.keys())}") except (ValueError, TypeError, SyntaxError): click.echo(f" Front matter: (parsing error)") click.echo() except Exception as e: click.echo(f"Error listing files: {e}", err=True) if config['verbose']: import traceback click.echo(traceback.format_exc(), err=True) sys.exit(1) def main(): """ Main entry point for the CLI. This function is referenced in pyproject.toml console_scripts. """ try: cli() except KeyboardInterrupt: click.echo("\nOperation interrupted by user.", err=True) sys.exit(130) # Standard exit code for SIGINT except Exception as e: click.echo(f"Unexpected error: {e}", err=True) sys.exit(1) if __name__ == '__main__': main()