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 for sync: {}", e))?;
30 let app = Application::new(database);
31 app.sync_directory(&metis_dir)
32 .await
33 .map_err(|e| anyhow::anyhow!("Failed to sync workspace: {}", e))?;
34
35 let database = Database::new(db_path.to_str().unwrap())
37 .map_err(|e| anyhow::anyhow!("Failed to open database: {}", e))?;
38 let mut app = Application::new(database);
39
40 let results = self.perform_search(&mut app, &self.query)?;
42
43 let limited_results: Vec<_> = results.into_iter().take(self.limit).collect();
45
46 self.display_results(&limited_results)?;
48
49 Ok(())
50 }
51
52 fn perform_search(&self, app: &mut Application, query: &str) -> Result<Vec<Document>> {
53 app.with_database(|db_service| db_service.search_documents(query))
54 .map_err(|e| anyhow::anyhow!("Search failed: {}", e))
55 }
56
57 fn display_results(&self, documents: &[Document]) -> Result<()> {
58 if documents.is_empty() {
59 println!("No documents found for query: \"{}\"", self.query);
60 return Ok(());
61 }
62
63 self.display_table(documents)?;
64 Ok(())
65 }
66
67 fn display_table(&self, documents: &[Document]) -> Result<()> {
68 println!("{:<50} {:<12} {:<120}", "ID", "Type", "Path");
70 println!("{}", "-".repeat(182));
71
72 for doc in documents {
73 let id = truncate(&doc.id, 49);
74 let doc_type = truncate(&doc.document_type, 11);
75
76 let path = truncate(&doc.filepath, 119);
78
79 println!("{:<50} {:<12} {:<120}", id, doc_type, path);
80 }
81
82 println!(
83 "\nFound {} document(s) for \"{}\"",
84 documents.len(),
85 self.query
86 );
87 Ok(())
88 }
89}
90
91fn truncate(s: &str, max_len: usize) -> String {
93 if s.len() <= max_len {
94 s.to_string()
95 } else {
96 format!("{}...", &s[..max_len.saturating_sub(3)])
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_truncate() {
106 assert_eq!(truncate("short", 10), "short");
107 assert_eq!(truncate("this is a very long string", 10), "this is...");
108 assert_eq!(truncate("exactly_10", 10), "exactly_10");
109 }
110}