#[allow(dead_code)]
mod csv;
#[allow(dead_code)]
mod json;
#[allow(dead_code)]
mod markdown;
#[allow(dead_code)]
mod table;
#[allow(unused_imports)]
pub use self::csv::CsvOutput;
#[allow(unused_imports)]
pub use self::json::JsonOutput;
#[allow(unused_imports)]
pub use self::markdown::MarkdownOutput;
#[allow(unused_imports)]
pub use self::table::TableOutput;
use crate::cli::args::OutputFormat;
use crate::error::Result;
use serde::Serialize;
#[allow(dead_code)]
pub trait Render {
fn render(&self, format: OutputFormat, pretty: bool) -> Result<String>;
}
#[allow(dead_code)]
pub struct Output {
format: OutputFormat,
pretty: bool,
}
#[allow(dead_code)]
impl Output {
pub fn new(format: OutputFormat, pretty: bool) -> Self {
Self { format, pretty }
}
pub fn auto_detect(pretty: bool) -> Self {
let format = if atty::is(atty::Stream::Stdout) {
OutputFormat::Table
} else {
OutputFormat::Json
};
Self { format, pretty }
}
pub fn effective_format(&self) -> OutputFormat {
match self.format {
OutputFormat::Auto => {
if atty::is(atty::Stream::Stdout) {
OutputFormat::Table
} else {
OutputFormat::Json
}
}
other => other,
}
}
pub fn print<T: Serialize + Tabular>(&self, data: &T) -> Result<()> {
match self.effective_format() {
OutputFormat::Json => {
JsonOutput::new(self.pretty).print(data)?;
}
OutputFormat::Jsonl => {
JsonOutput::new(false).print_jsonl(data)?;
}
OutputFormat::Csv => {
CsvOutput::new().print(data)?;
}
OutputFormat::Table => {
TableOutput::new().print(data)?;
}
OutputFormat::Markdown => {
MarkdownOutput::new().print(data)?;
}
OutputFormat::Auto => unreachable!(),
}
Ok(())
}
pub fn print_list<T: Serialize + Tabular>(&self, items: &[T]) -> Result<()> {
match self.effective_format() {
OutputFormat::Jsonl => {
for item in items {
JsonOutput::new(false).print(item)?;
}
}
OutputFormat::Json => {
JsonOutput::new(self.pretty).print(items)?;
}
OutputFormat::Csv => {
CsvOutput::new().print_rows(items)?;
}
OutputFormat::Table => {
TableOutput::new().print_rows(items)?;
}
OutputFormat::Markdown => {
MarkdownOutput::new().print_rows(items)?;
}
OutputFormat::Auto => {
if atty::is(atty::Stream::Stdout) {
TableOutput::new().print_rows(items)?;
} else {
JsonOutput::new(false).print(items)?;
}
}
}
Ok(())
}
}
#[allow(dead_code)]
pub trait Tabular {
fn headers() -> Vec<String>;
fn row(&self) -> Vec<String>;
}
impl<T: Tabular> Tabular for Vec<T> {
fn headers() -> Vec<String> {
T::headers()
}
fn row(&self) -> Vec<String> {
vec![]
}
}
use crate::cli::args::GlobalArgs;
pub struct OutputWriter<'a> {
global: &'a GlobalArgs,
}
impl<'a> OutputWriter<'a> {
pub fn new(global: &'a GlobalArgs) -> Self {
Self { global }
}
pub fn format(&self) -> OutputFormat {
self.global.effective_output_format()
}
pub fn pretty(&self) -> bool {
self.global.should_pretty_print()
}
pub fn write_value<T: Serialize>(&self, value: &T) -> Result<()> {
let format = self.format();
let pretty = self.pretty();
match format {
OutputFormat::Auto => {
if atty::is(atty::Stream::Stdout) {
let json = if pretty {
serde_json::to_string_pretty(value)?
} else {
serde_json::to_string(value)?
};
println!("{}", json);
} else {
let json = serde_json::to_string(value)?;
println!("{}", json);
}
}
OutputFormat::Json => {
let json = if pretty {
serde_json::to_string_pretty(value)?
} else {
serde_json::to_string(value)?
};
println!("{}", json);
}
OutputFormat::Jsonl => {
let json_value = serde_json::to_value(value)?;
if let serde_json::Value::Array(arr) = json_value {
for item in arr {
println!("{}", serde_json::to_string(&item)?);
}
} else {
println!("{}", serde_json::to_string(&json_value)?);
}
}
OutputFormat::Csv => {
let json_value = serde_json::to_value(value)?;
self.write_csv(&json_value)?;
}
OutputFormat::Table => {
let json = serde_json::to_string_pretty(value)?;
println!("{}", json);
}
OutputFormat::Markdown => {
let json = serde_json::to_string_pretty(value)?;
println!("```json\n{}\n```", json);
}
}
Ok(())
}
fn write_csv(&self, value: &serde_json::Value) -> Result<()> {
#[allow(unused_imports)]
use std::io::Write;
let mut wtr = ::csv::Writer::from_writer(std::io::stdout());
match value {
serde_json::Value::Array(arr) => {
if let Some(first) = arr.first() {
if let serde_json::Value::Object(obj) = first {
let headers: Vec<&str> = obj.keys().map(|k| k.as_str()).collect();
wtr.write_record(&headers)?;
for item in arr {
if let serde_json::Value::Object(row) = item {
let values: Vec<String> = headers.iter()
.map(|h| {
row.get(*h)
.map(|v| match v {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Null => String::new(),
other => other.to_string(),
})
.unwrap_or_default()
})
.collect();
wtr.write_record(&values)?;
}
}
}
}
}
serde_json::Value::Object(obj) => {
wtr.write_record(&["key", "value"])?;
for (k, v) in obj {
let value_str = match v {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Null => String::new(),
other => other.to_string(),
};
wtr.write_record(&[k.as_str(), &value_str])?;
}
}
_ => {
println!("{}", value);
}
}
wtr.flush()?;
Ok(())
}
}