oxur_cli/common/
io.rs

1//! File I/O utilities for CLI tools
2//!
3//! Provides helpers for reading from stdin/file and writing to stdout/file,
4//! which is a common pattern in CLI tools that support piping.
5
6use anyhow::Result;
7use std::fs;
8use std::io::{self, Read, Write};
9use std::path::Path;
10
11/// Read input from stdin (if path is "-") or from a file
12///
13/// # Examples
14///
15/// ```no_run
16/// use std::path::PathBuf;
17/// use oxur_cli::common::io::read_input;
18///
19/// // Read from file
20/// let content = read_input(&PathBuf::from("input.txt"))?;
21///
22/// // Read from stdin (if user passes "-")
23/// let content = read_input(&PathBuf::from("-"))?;
24/// # Ok::<(), anyhow::Error>(())
25/// ```
26pub fn read_input(path: &Path) -> Result<String> {
27    if path.to_str() == Some("-") {
28        let mut buffer = String::new();
29        io::stdin().read_to_string(&mut buffer)?;
30        Ok(buffer)
31    } else {
32        Ok(fs::read_to_string(path)?)
33    }
34}
35
36/// Write output to stdout (if path is None or "-") or to a file
37///
38/// # Examples
39///
40/// ```no_run
41/// use std::path::PathBuf;
42/// use oxur_cli::common::io::write_output;
43///
44/// // Write to file
45/// write_output("content", Some(&PathBuf::from("output.txt")))?;
46///
47/// // Write to stdout
48/// write_output("content", None)?;
49///
50/// // Write to stdout (if user passes "-")
51/// write_output("content", Some(&PathBuf::from("-")))?;
52/// # Ok::<(), anyhow::Error>(())
53/// ```
54pub fn write_output(content: &str, path: Option<&Path>) -> Result<()> {
55    match path {
56        Some(p) if p.to_str() == Some("-") => {
57            println!("{}", content);
58            Ok(())
59        }
60        Some(p) => {
61            fs::write(p, content)?;
62            Ok(())
63        }
64        None => {
65            println!("{}", content);
66            Ok(())
67        }
68    }
69}
70
71/// Write output to stderr
72///
73/// Useful for progress messages and diagnostics that shouldn't be mixed with stdout.
74///
75/// # Examples
76///
77/// ```no_run
78/// use oxur_cli::common::io::write_stderr;
79///
80/// write_stderr("Processing file...")?;
81/// # Ok::<(), anyhow::Error>(())
82/// ```
83pub fn write_stderr(message: &str) -> Result<()> {
84    writeln!(io::stderr(), "{}", message)?;
85    Ok(())
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use std::path::PathBuf;
92    use tempfile::TempDir;
93
94    #[test]
95    fn test_read_input_from_file() {
96        let temp = TempDir::new().unwrap();
97        let file_path = temp.path().join("test.txt");
98        fs::write(&file_path, "test content").unwrap();
99
100        let content = read_input(&file_path).unwrap();
101        assert_eq!(content, "test content");
102    }
103
104    #[test]
105    fn test_read_input_nonexistent_file() {
106        let path = PathBuf::from("nonexistent.txt");
107        let result = read_input(&path);
108        assert!(result.is_err());
109    }
110
111    #[test]
112    fn test_write_output_to_file() {
113        let temp = TempDir::new().unwrap();
114        let file_path = temp.path().join("output.txt");
115
116        write_output("test content", Some(&file_path)).unwrap();
117
118        let content = fs::read_to_string(&file_path).unwrap();
119        assert_eq!(content, "test content");
120    }
121
122    #[test]
123    fn test_write_output_to_stdout() {
124        // This test just verifies it doesn't panic
125        let result = write_output("test", None);
126        assert!(result.is_ok());
127    }
128
129    #[test]
130    fn test_write_output_dash_means_stdout() {
131        // Verify that "-" is treated as stdout
132        let result = write_output("test", Some(&PathBuf::from("-")));
133        assert!(result.is_ok());
134    }
135
136    #[test]
137    fn test_write_stderr() {
138        let result = write_stderr("test message");
139        assert!(result.is_ok());
140    }
141}