use crate::error::{io_error_with_path, FoundationError, Result};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::fs;
use std::io::{self, Read, Write};
use std::path::Path;
pub fn read_input<P: AsRef<Path>>(file: Option<P>, inline: Option<String>) -> Result<String> {
if let Some(filename) = file {
let path = filename.as_ref();
fs::read_to_string(path).map_err(|e| io_error_with_path(e, path, "read file"))
} else if let Some(content) = inline {
Ok(content)
} else {
let mut buffer = String::new();
io::stdin()
.read_to_string(&mut buffer)
.map_err(|e| FoundationError::Io(e).with_context("Failed to read from stdin"))?;
Ok(buffer)
}
}
pub fn read_json<T: DeserializeOwned>(path_str: &str) -> Result<T> {
let path = Path::new(path_str);
let content = fs::read_to_string(path).map_err(|e| io_error_with_path(e, path, "read"))?;
serde_json::from_str(&content).map_err(|e| {
FoundationError::Serialization(e)
.with_context(&format!("Failed to parse JSON from {path_str}"))
})
}
pub fn write_output(path: Option<&Path>, content: &str) -> Result<()> {
match path {
Some(file_path) => {
fs::write(file_path, content).map_err(|e| io_error_with_path(e, file_path, "write to"))
}
None => io::stdout()
.write_all(content.as_bytes())
.map_err(|e| FoundationError::Io(e).with_context("Failed to write to stdout")),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputFormat {
Text,
Json,
JsonCompact,
Debug,
DebugPretty,
}
impl std::str::FromStr for OutputFormat {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"text" => Ok(OutputFormat::Text),
"json" => Ok(OutputFormat::Json),
"json-compact" | "compact" => Ok(OutputFormat::JsonCompact),
"debug" => Ok(OutputFormat::Debug),
"debug-pretty" | "pretty" => Ok(OutputFormat::DebugPretty),
_ => Err(format!(
"Invalid output format: {s}. Valid options: text, json, compact, debug, pretty"
)),
}
}
}
pub fn print_with_format<T>(value: &T, format: OutputFormat) -> Result<()>
where
T: std::fmt::Debug + Serialize,
{
match format {
OutputFormat::Json => {
let json = serde_json::to_string_pretty(value).map_err(|e| {
FoundationError::Serialization(e).with_context("Failed to serialize JSON")
})?;
println!("{json}");
}
OutputFormat::JsonCompact => {
let json = serde_json::to_string(value).map_err(|e| {
FoundationError::Serialization(e).with_context("Failed to serialize JSON")
})?;
println!("{json}");
}
OutputFormat::Debug => {
println!("{value:?}");
}
OutputFormat::DebugPretty => {
println!("{value:#?}");
}
OutputFormat::Text => {
println!("{value:?}");
}
}
Ok(())
}
pub fn print_result<T, E>(result: std::result::Result<T, E>) -> bool
where
T: std::fmt::Display,
E: std::fmt::Display,
{
match result {
Ok(value) => {
println!("{value}");
true
}
Err(e) => {
eprintln!("Error: {e}");
false
}
}
}
pub fn exit_if(condition: bool, code: i32) {
if condition {
std::process::exit(code);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_output_format_parsing() {
assert_eq!("text".parse::<OutputFormat>().unwrap(), OutputFormat::Text);
assert_eq!("json".parse::<OutputFormat>().unwrap(), OutputFormat::Json);
assert_eq!(
"compact".parse::<OutputFormat>().unwrap(),
OutputFormat::JsonCompact
);
assert_eq!(
"debug".parse::<OutputFormat>().unwrap(),
OutputFormat::Debug
);
assert_eq!(
"pretty".parse::<OutputFormat>().unwrap(),
OutputFormat::DebugPretty
);
assert!("invalid".parse::<OutputFormat>().is_err());
}
#[test]
fn test_read_json() {
let temp_dir = std::env::temp_dir();
let test_file = temp_dir.join("test_cli_json.json");
let test_data = r#"{"name": "test", "value": 42}"#;
fs::write(&test_file, test_data).unwrap();
#[derive(serde::Deserialize, PartialEq, Debug)]
struct TestData {
name: String,
value: i32,
}
let result: TestData = read_json(test_file.to_str().unwrap()).unwrap();
assert_eq!(result.name, "test");
assert_eq!(result.value, 42);
fs::remove_file(test_file).unwrap();
}
}