use clap::{Parser, Subcommand};
use log::info;
use directory_indexer::*;
#[derive(Parser)]
#[command(name = "directory-indexer")]
#[command(about = "AI-powered directory indexing with semantic search")]
#[command(version)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long, global = true)]
verbose: bool,
#[arg(short, long, global = true)]
config: Option<String>,
}
#[derive(Subcommand)]
enum Commands {
Index {
paths: Vec<String>,
},
Search {
query: String,
#[arg(short, long)]
path: Option<String>,
#[arg(short, long)]
limit: Option<usize>,
},
Similar {
file: String,
#[arg(short, long, default_value = "10")]
limit: usize,
},
Get {
file: String,
#[arg(long)]
chunks: Option<String>,
},
Serve,
Status {
#[arg(short, long, default_value = "text")]
format: String,
},
}
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
let log_level = if cli.verbose { "debug" } else { "info" };
std::env::set_var("RUST_LOG", log_level);
env_logger::Builder::from_default_env()
.format_timestamp(None)
.format_target(false)
.format_module_path(false)
.init();
info!("Starting directory-indexer");
match cli.command {
Commands::Index { paths } => {
cli::commands::index(paths).await?;
}
Commands::Search { query, path, limit } => {
cli::commands::search(query, path, limit).await?;
}
Commands::Similar { file, limit } => {
cli::commands::similar(file, limit).await?;
}
Commands::Get { file, chunks } => {
cli::commands::get(file, chunks).await?;
}
Commands::Serve => {
cli::commands::serve().await?;
}
Commands::Status { format } => {
cli::commands::status(format).await?;
}
}
Ok(())
}
pub fn setup_logging(verbose: bool) {
let log_level = if verbose { "debug" } else { "info" };
std::env::set_var("RUST_LOG", log_level);
env_logger::Builder::from_default_env()
.format_timestamp(None)
.format_target(false)
.format_module_path(false)
.init();
}
pub fn get_log_level(verbose: bool) -> &'static str {
if verbose {
"debug"
} else {
"info"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_log_level() {
assert_eq!(get_log_level(true), "debug");
assert_eq!(get_log_level(false), "info");
}
#[test]
fn test_cli_structure() {
let cli = Cli::parse_from(["directory-indexer", "serve"]);
assert!(!cli.verbose); assert!(cli.config.is_none());
let cli = Cli::parse_from(["directory-indexer", "-v", "serve"]);
assert!(cli.verbose);
let cli = Cli::parse_from(["directory-indexer", "--config", "test.toml", "serve"]);
assert_eq!(cli.config, Some("test.toml".to_string()));
}
#[test]
fn test_commands_enum_parsing() {
let test_cases = vec![
(vec!["directory-indexer", "index", "/path"], "index"),
(vec!["directory-indexer", "search", "query"], "search"),
(vec!["directory-indexer", "similar", "file.txt"], "similar"),
(vec!["directory-indexer", "get", "file.txt"], "get"),
(vec!["directory-indexer", "serve"], "serve"),
(vec!["directory-indexer", "status"], "status"),
];
for (args, expected_cmd) in test_cases {
let cli = Cli::parse_from(args);
match (&cli.command, expected_cmd) {
(Commands::Index { .. }, "index") => {}
(Commands::Search { .. }, "search") => {}
(Commands::Similar { .. }, "similar") => {}
(Commands::Get { .. }, "get") => {}
(Commands::Serve, "serve") => {}
(Commands::Status { .. }, "status") => {}
_ => panic!("Unexpected command type for {expected_cmd}"),
}
}
}
#[test]
fn test_command_with_global_args() {
let cli = Cli::parse_from([
"directory-indexer",
"-v",
"--config",
"/path/to/config.toml",
"search",
"test query",
"--limit",
"5",
]);
assert!(cli.verbose);
assert_eq!(cli.config, Some("/path/to/config.toml".to_string()));
match cli.command {
Commands::Search { query, limit, .. } => {
assert_eq!(query, "test query");
assert_eq!(limit, Some(5));
}
_ => panic!("Expected Search command"),
}
}
#[test]
fn test_help_content() {
use clap::CommandFactory;
let mut command = Cli::command();
let help_output = command.render_help().to_string();
assert!(help_output.contains("AI-powered directory indexing"));
assert!(help_output.contains("Usage:"));
assert!(help_output.contains("Commands:"));
assert!(help_output.contains("Options:"));
}
}