use anyhow::Result; use clap::Command; use std::io::{Read, Write}; use crate::config; use crate::services::item_service::ItemService; /// Validates save mode arguments and exits with error if invalid. /// /// This function checks that no item IDs are provided for save mode, /// as save operations create new items rather than modifying existing ones. /// /// # Arguments /// /// * `cmd` - Mutable reference to the Clap command for error reporting. /// * `ids` - Reference to the vector of item IDs (should be empty for save mode). /// /// # Panics /// /// Exits the program via Clap error if IDs are provided. fn validate_save_args(cmd: &mut Command, ids: &Vec) { if !ids.is_empty() { cmd.error( clap::error::ErrorKind::InvalidValue, "ID given, you cannot supply IDs when using --save", ) .exit(); } } /// A tee reader that duplicates input to both a reader and a writer as it reads. /// /// This struct implements the `Read` trait and forwards all read operations to /// an underlying reader while simultaneously writing the same data to a writer. /// It's useful for saving content to a file while also echoing it to stdout. /// /// # Fields /// /// * `reader` - The underlying reader providing the data source. /// * `writer` - The writer receiving copies of all read data. struct TeeReader { reader: R, writer: W, } impl Read for TeeReader { /// Reads data from the underlying reader and duplicates it to the writer. /// /// This implementation reads from the inner reader and then writes the same /// bytes to the writer. If the read returns 0 bytes (EOF), it returns 0. /// /// # Arguments /// /// * `buf` - Buffer to fill with data from the reader. /// /// # Returns /// /// * `io::Result` - Number of bytes read, or an I/O error. /// /// # Errors /// /// Returns an error if the underlying read or write operations fail. /// /// # Examples /// /// ``` /// let mut tee = TeeReader { /// reader: std::io::Cursor::new(b"Hello, world!"), /// writer: std::io::sink(), /// }; /// let mut buf = [0; 5]; /// let n = tee.read(&mut buf).unwrap(); /// assert_eq!(n, 5); /// assert_eq!(&buf[..n], b"Hello"); /// ``` fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let n = self.reader.read(buf)?; if n > 0 { self.writer.write_all(&buf[..n])?; } Ok(n) } } /// Main save mode function. /// /// This function handles the save operation by reading from stdin, duplicating /// the input to stdout (for real-time display), and saving the content to the /// item service. It validates arguments, creates the tee reader, and processes /// the save operation. /// /// # Arguments /// /// * `cmd` - Mutable reference to the Clap command for error handling. /// * `settings` - Application settings containing configuration. /// * `ids` - Mutable vector of item IDs (should be empty for save mode). /// * `tags` - Mutable vector of tags to associate with the new item. /// * `conn` - Mutable reference to the database connection. /// * `data_path` - Path to the data storage directory. /// /// # Returns /// /// * `Result<(), anyhow::Error>` - Success or error if save fails. /// /// # Examples /// /// ``` /// // In CLI context, this would be called internally /// mode_save(&mut cmd, &settings, &mut vec![], &mut vec!["important".to_string()], &mut conn, data_path)?; /// ``` pub fn mode_save( cmd: &mut Command, settings: &config::Settings, ids: &mut Vec, tags: &mut Vec, conn: &mut rusqlite::Connection, data_path: std::path::PathBuf, ) -> Result<(), anyhow::Error> { validate_save_args(cmd, ids); let item_service = ItemService::new(data_path); let stdin = std::io::stdin(); let stdout = std::io::stdout(); let tee_reader = TeeReader { reader: stdin.lock(), writer: stdout.lock(), }; item_service.save_item(tee_reader, cmd, settings, tags, conn)?; Ok(()) }