diff --git a/src/args.rs b/src/args.rs index cf5f19b..a699564 100644 --- a/src/args.rs +++ b/src/args.rs @@ -90,6 +90,10 @@ pub struct ItemArgs { #[arg(help_heading("Item Options"), short('M'), long, env("KEEP_META_PLUGINS"))] #[arg(help("Meta plugins to use when saving items"))] pub meta_plugins: Vec, + + #[arg(help_heading("Item Options"), long, env("KEEP_FILTERS"))] + #[arg(help("Filter string to apply to content when getting items"))] + pub filters: Option, } diff --git a/src/main.rs b/src/main.rs index 8ccffb6..97e9d5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -176,12 +176,24 @@ fn main() -> Result<(), Error> { // Initialize database let mut conn = db::open(db_path.clone())?; - // Create an empty filters vector for the get mode - let filters: Vec = Vec::new(); + // Parse filter chain early for better error reporting + let filter_chain = if let Some(filter_str) = &args.item.filters { + match keep::filter_plugin::parse_filter_string(filter_str) { + Ok(chain) => Some(chain), + Err(e) => { + cmd.error( + ErrorKind::InvalidValue, + format!("Invalid filter string: {}", e) + ).exit(); + } + } + } else { + None + }; match mode { KeepModes::Save => modes::save::mode_save(&mut cmd, &settings, ids, tags, &mut conn, data_path), - KeepModes::Get => modes::get::mode_get(&mut cmd, &settings, ids, tags, &mut conn, data_path, &filters), + KeepModes::Get => modes::get::mode_get(&mut cmd, &settings, ids, tags, &mut conn, data_path, filter_chain), KeepModes::Diff => modes::diff::mode_diff(&mut cmd, &settings, &settings, ids, tags, &mut conn, data_path), KeepModes::List => modes::list::mode_list(&mut cmd, &settings, ids, tags, &mut conn, data_path), KeepModes::Delete => modes::delete::mode_delete(&mut cmd, &settings, &settings, ids, tags, &mut conn, data_path), diff --git a/src/modes/get.rs b/src/modes/get.rs index 157ed4f..23d2a3e 100644 --- a/src/modes/get.rs +++ b/src/modes/get.rs @@ -4,6 +4,7 @@ use std::io::Write; use crate::common::is_binary::is_binary; use crate::common::PIPESIZE; use crate::config; +use crate::filter_plugin::FilterChain; use crate::services::item_service::ItemService; use clap::Command; use is_terminal::IsTerminal; @@ -17,7 +18,7 @@ pub fn mode_get( tags: &mut Vec, conn: &mut rusqlite::Connection, data_path: PathBuf, - filters: &Vec, + filter_chain: Option, ) -> Result<()> { if !ids.is_empty() && !tags.is_empty() { cmd.error(clap::error::ErrorKind::InvalidValue, "Both ID and tags given, you must supply either IDs or tags when using --get").exit(); @@ -48,18 +49,11 @@ pub fn mode_get( } } - // Join all filter strings with | to create a single filter string - let filter_str = if filters.is_empty() { - None - } else { - Some(filters.join(" | ")) - }; - - // Get a reader that applies the filters - let (mut reader, _, _) = item_service.get_item_content_info_streaming( + // Get a reader that applies the filters using the pre-parsed filter chain + let (mut reader, _, _) = item_service.get_item_content_info_streaming_with_chain( conn, item_id, - filter_str.clone(), + filter_chain, )?; if detect_binary { @@ -72,10 +66,10 @@ pub fn mode_get( )); } // We need to create a new reader since we consumed some bytes - let (new_reader, _, _) = item_service.get_item_content_info_streaming( + let (new_reader, _, _) = item_service.get_item_content_info_streaming_with_chain( conn, item_id, - filter_str.clone(), + filter_chain, )?; reader = new_reader; } diff --git a/src/services/item_service.rs b/src/services/item_service.rs index d6ad259..2939d8b 100644 --- a/src/services/item_service.rs +++ b/src/services/item_service.rs @@ -181,6 +181,23 @@ impl ItemService { conn: &Connection, id: i64, filter: Option, + ) -> Result<(Box, String, bool), CoreError> { + // Convert filter string to FilterChain if provided + let filter_chain = if let Some(filter_str) = filter { + self.filter_service.create_filter_chain(Some(&filter_str)) + .map_err(|e| CoreError::InvalidInput(format!("Failed to create filter chain: {}", e)))? + } else { + None + }; + + self.get_item_content_info_streaming_with_chain(conn, id, filter_chain) + } + + pub fn get_item_content_info_streaming_with_chain( + &self, + conn: &Connection, + id: i64, + filter_chain: Option, ) -> Result<(Box, String, bool), CoreError> { let item_with_meta = self.get_item(conn, id)?; let item_id = item_with_meta.item.id.ok_or_else(|| CoreError::InvalidInput("Item missing ID".to_string()))?; @@ -197,10 +214,6 @@ impl ItemService { &item_with_meta.item.compression )?; - // Create filter chain - let filter_chain = self.filter_service.create_filter_chain(filter.as_deref()) - .map_err(|e| CoreError::InvalidInput(format!("Failed to create filter chain: {}", e)))?; - // Wrap the reader with filtering let filtered_reader = Box::new(FilteringReader::new(reader, filter_chain));