metis_docs_cli/commands/
search.rs1use crate::workspace;
2use anyhow::Result;
3use clap::Args;
4use metis_core::dal::database::models::Document;
5use metis_core::{Application, Database};
6
7#[derive(Args)]
8pub struct SearchCommand {
9 pub query: String,
11
12 #[arg(short = 'l', long, default_value = "20")]
14 pub limit: usize,
15}
16
17impl SearchCommand {
18 pub async fn execute(&self) -> Result<()> {
19 let (workspace_exists, metis_dir) = workspace::has_metis_vault();
21 if !workspace_exists {
22 anyhow::bail!("Not in a Metis workspace. Run 'metis init' to create one.");
23 }
24 let metis_dir = metis_dir.unwrap();
25
26 let db_path = metis_dir.join("metis.db");
28 let database = Database::new(db_path.to_str().unwrap())
29 .map_err(|e| anyhow::anyhow!("Failed to open database: {}", e))?;
30 let mut app = Application::new(database);
31
32 let results = self.perform_search(&mut app, &self.query)?;
34
35 let limited_results: Vec<_> = results.into_iter().take(self.limit).collect();
37
38 self.display_results(&limited_results)?;
40
41 Ok(())
42 }
43
44 fn perform_search(&self, app: &mut Application, query: &str) -> Result<Vec<Document>> {
45 app.with_database(|db_service| db_service.search_documents(query))
46 .map_err(|e| anyhow::anyhow!("Search failed: {}", e))
47 }
48
49 fn display_results(&self, documents: &[Document]) -> Result<()> {
50 if documents.is_empty() {
51 println!("No documents found for query: \"{}\"", self.query);
52 return Ok(());
53 }
54
55 self.display_table(documents)?;
56 Ok(())
57 }
58
59 fn display_table(&self, documents: &[Document]) -> Result<()> {
60 println!("{:<50} {:<12} {:<120}", "ID", "Type", "Path");
62 println!("{}", "-".repeat(182));
63
64 for doc in documents {
65 let id = truncate(&doc.id, 49);
66 let doc_type = truncate(&doc.document_type, 11);
67
68 let relative_path = if let Some(metis_pos) = doc.filepath.find(".metis/") {
70 &doc.filepath[metis_pos + 7..] } else {
72 &doc.filepath
73 };
74 let path = truncate(relative_path, 119);
75
76 println!("{:<50} {:<12} {:<120}", id, doc_type, path);
77 }
78
79 println!(
80 "\nFound {} document(s) for \"{}\"",
81 documents.len(),
82 self.query
83 );
84 Ok(())
85 }
86}
87
88fn truncate(s: &str, max_len: usize) -> String {
90 if s.len() <= max_len {
91 s.to_string()
92 } else {
93 format!("{}...", &s[..max_len.saturating_sub(3)])
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_truncate() {
103 assert_eq!(truncate("short", 10), "short");
104 assert_eq!(truncate("this is a very long string", 10), "this is...");
105 assert_eq!(truncate("exactly_10", 10), "exactly_10");
106 }
107}