use clap::{Parser, Subcommand}; use std::path::Path; #[derive(Parser)] #[command(name = "markbase", about = "Momentry Display Engine")] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Start display server Display { #[arg(short, long, default_value = "11438")] port: u16, /// Optional initial markdown file file: Option, }, /// Render markdown to HTML (stdout) Render { file: String, #[arg(short, long)] output: Option, }, /// Configuration management Config { #[command(subcommand)] action: ConfigCommands, }, /// Scan and import files from directory Scan { /// User ID #[arg(short, long)] user: String, /// Directory to scan #[arg(short, long)] dir: String, /// Batch size for database insertion #[arg(short, long, default_value = "100")] batch: usize, /// Skip SHA256 hash calculation (faster import) #[arg(short, long, default_value = "true")] skip_hash: bool, /// Number of threads for hash calculation (if skip_hash=false) #[arg(short, long, default_value = "4")] threads: usize, }, /// Compute SHA256 hashes for imported files Hash { /// User ID #[arg(short, long)] user: String, /// Number of threads for parallel hash calculation #[arg(short, long, default_value = "4")] threads: usize, }, } #[derive(Subcommand)] enum ConfigCommands { /// Initialize default configuration file Init { #[arg(short, long)] force: bool, }, /// Show current configuration Show { #[arg(short, long)] section: Option, }, /// Edit configuration Edit { #[arg(short, long)] key: String, #[arg(short, long)] value: String, }, /// Validate configuration Validate, } #[tokio::main] async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { Commands::Display { port, file } => { markbase::server::run(port, file).await?; } Commands::Render { file, output } => { let md = std::fs::read_to_string(&file)?; let html = markbase::render::md_to_html(&md); if let Some(path) = &output { std::fs::write(path, html)?; } else { println!("{html}"); } } Commands::Config { action } => { handle_config_command(action)?; } Commands::Scan { user, dir, batch, skip_hash, threads } => { use markbase::scan::ScanOptions; let options = ScanOptions { skip_hash, threads, }; markbase::scan::scan_directory(&user, &dir, batch, options)?; } Commands::Hash { user, threads } => { markbase::scan::compute_hashes(&user, threads)?; } } Ok(()) } fn handle_config_command(action: ConfigCommands) -> anyhow::Result<()> { match action { ConfigCommands::Init { force } => { let config_path = Path::new("config/markbase.toml"); if config_path.exists() && !force { println!("Configuration file already exists at config/markbase.toml"); println!("Use --force to overwrite"); return Ok(()); } let config = markbase::config::MarkBaseConfig::default_config(); config.save(config_path)?; println!("✓ Configuration file created: config/markbase.toml"); println!("Default values:"); println!(" Server port: {}", config.server.port); println!(" PostgreSQL host: {}", config.postgresql.host); println!(" Test users: {}", config.test.users.join(", ")); } ConfigCommands::Show { section } => { let config_path = Path::new("config/markbase.toml"); if !config_path.exists() { println!("Configuration file not found. Run 'markbase config init' first."); return Ok(()); } let config = markbase::config::MarkBaseConfig::load(config_path)?; if let Some(s) = section { show_section(&config, &s); } else { println!("{}", toml::to_string_pretty(&config)?); } } ConfigCommands::Edit { key, value } => { let config_path = Path::new("config/markbase.toml"); if !config_path.exists() { println!("Configuration file not found. Run 'markbase config init' first."); return Ok(()); } let mut config = markbase::config::MarkBaseConfig::load(config_path)?; match config.get(&key) { Some(old_value) => { config.set(&key, &value)?; config.validate()?; config.save(config_path)?; println!("✓ Updated {}: {} → {}", key, old_value, value); } None => { println!("Invalid config key: {}", key); println!("Valid keys: server.*, postgresql.*, authentication.*, test.*, logging.*"); } } } ConfigCommands::Validate => { let config_path = Path::new("config/markbase.toml"); if !config_path.exists() { println!("Configuration file not found. Run 'markbase config init' first."); return Ok(()); } let config = markbase::config::MarkBaseConfig::load(config_path)?; match config.validate() { Ok(_) => { println!("✓ Configuration is valid"); } Err(e) => { println!("✗ Configuration validation failed: {}", e); } } } } Ok(()) } fn show_section(config: &markbase::config::MarkBaseConfig, section: &str) { match section { "server" => println!("{}", toml::to_string_pretty(&config.server).unwrap()), "postgresql" => println!("{}", toml::to_string_pretty(&config.postgresql).unwrap()), "authentication" => println!("{}", toml::to_string_pretty(&config.authentication).unwrap()), "test" => println!("{}", toml::to_string_pretty(&config.test).unwrap()), "logging" => println!("{}", toml::to_string_pretty(&config.logging).unwrap()), _ => println!("Invalid section: {}. Valid sections: server, postgresql, authentication, test, logging", section), } }