use anyhow::Result;
use console::Term;
use serde::Serialize;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Table,
Json,
Yaml,
Csv,
Plain,
}
impl FromStr for OutputFormat {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"table" => Ok(OutputFormat::Table),
"json" => Ok(OutputFormat::Json),
"yaml" | "yml" => Ok(OutputFormat::Yaml),
"csv" => Ok(OutputFormat::Csv),
"plain" => Ok(OutputFormat::Plain),
_ => anyhow::bail!(
"Invalid output format: {}. Use: table, json, yaml, csv, plain",
s
),
}
}
}
impl OutputFormat {
pub fn determine(cli_format: Option<OutputFormat>) -> OutputFormat {
if let Some(format) = cli_format {
return format;
}
if let Ok(env_format) = std::env::var("RAPS_OUTPUT_FORMAT")
&& let Ok(format) = OutputFormat::from_str(&env_format)
{
return format;
}
if !Term::stdout().is_term() {
return OutputFormat::Json;
}
OutputFormat::Table
}
pub fn write<T: Serialize>(&self, data: &T) -> Result<()> {
match self {
OutputFormat::Table => write_table(data),
OutputFormat::Json => write_json(data),
OutputFormat::Yaml => write_yaml(data),
OutputFormat::Csv => write_csv(data),
OutputFormat::Plain => write_plain(data),
}
}
pub fn write_message(&self, message: &str) -> Result<()> {
match self {
OutputFormat::Table | OutputFormat::Plain => {
println!("{}", message);
Ok(())
}
OutputFormat::Json => {
#[derive(Serialize)]
struct Message {
message: String,
}
write_json(&Message {
message: message.to_string(),
})
}
OutputFormat::Yaml => {
#[derive(Serialize)]
struct Message {
message: String,
}
write_yaml(&Message {
message: message.to_string(),
})
}
OutputFormat::Csv => {
println!("{}", message);
Ok(())
}
}
}
pub fn supports_colors(&self) -> bool {
matches!(self, OutputFormat::Table)
}
}
fn write_json<T: Serialize>(data: &T) -> Result<()> {
let json = serde_json::to_string_pretty(data)?;
println!("{}", json);
Ok(())
}
fn write_yaml<T: Serialize>(data: &T) -> Result<()> {
let yaml = serde_yaml::to_string(data)?;
print!("{}", yaml);
Ok(())
}
fn write_csv<T: Serialize>(data: &T) -> Result<()> {
let json_value = serde_json::to_value(data)?;
match json_value {
serde_json::Value::Array(items) if !items.is_empty() => {
if let Some(serde_json::Value::Object(map)) = items.first() {
let mut wtr = csv::Writer::from_writer(std::io::stdout());
let headers: Vec<String> = map.keys().cloned().collect();
wtr.write_record(&headers)?;
for item in items {
if let serde_json::Value::Object(map) = item {
let mut row = Vec::new();
for header in &headers {
let value = map.get(header).unwrap_or(&serde_json::Value::Null);
row.push(format_value_for_csv(value));
}
wtr.write_record(&row)?;
}
}
wtr.flush()?;
return Ok(());
}
}
_ => {
return write_json(data);
}
}
write_json(data)
}
fn format_value_for_csv(value: &serde_json::Value) -> String {
match value {
serde_json::Value::Null => String::new(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Array(arr) => {
arr.iter()
.map(format_value_for_csv)
.collect::<Vec<_>>()
.join("; ")
}
serde_json::Value::Object(obj) => {
serde_json::to_string(obj).unwrap_or_default()
}
}
}
fn write_plain<T: Serialize>(data: &T) -> Result<()> {
let json = serde_json::to_string_pretty(data)?;
println!("{}", json);
Ok(())
}
fn write_table<T: Serialize>(data: &T) -> Result<()> {
write_json(data)
}
#[allow(dead_code)] pub trait TableFormat {
fn write_table(&self) -> Result<()>;
}