use std::{fs, path::Path};
use anyhow::{Result, anyhow, bail};
use crate::cli::OutputFormat;
pub(crate) fn write_query_result(
result: &query_forge::QueryResult,
output_path: Option<&Path>,
explicit_format: Option<OutputFormat>,
) -> Result<()> {
match resolve_output_format(output_path, explicit_format)? {
OutputFormat::Text => {
let rendered = query_forge::render_text(result);
write_or_print(&rendered, output_path)?;
}
OutputFormat::Csv => {
let rendered = query_forge::render_csv(result);
write_or_print(&rendered, output_path)?;
}
OutputFormat::Jsonl => {
let rendered = query_forge::render_jsonl(result);
write_or_print(&rendered, output_path)?;
}
OutputFormat::Markdown => {
let rendered = query_forge::render_markdown(result);
write_or_print(&rendered, output_path)?;
}
OutputFormat::Xml => {
let rendered = query_forge::render_xml(result);
write_or_print(&rendered, output_path)?;
}
OutputFormat::Xlsx => {
let output_path = output_path
.ok_or_else(|| anyhow!("--output is required when --format xlsx is selected"))?;
query_forge::write_xlsx(result, output_path)?;
}
}
Ok(())
}
fn write_or_print(rendered: &str, output_path: Option<&Path>) -> Result<()> {
if let Some(output_path) = output_path {
fs::write(output_path, rendered)?;
} else {
println!("{rendered}");
}
Ok(())
}
pub(crate) fn resolve_output_format(
output: Option<&Path>,
format: Option<OutputFormat>,
) -> Result<OutputFormat> {
if let Some(format) = format {
if matches!(format, OutputFormat::Xlsx) && output.is_none() {
bail!("--output is required when --format xlsx is selected");
}
return Ok(format);
}
if output
.and_then(|path| path.extension())
.map(|extension| extension.to_string_lossy())
.is_some_and(|extension| extension.eq_ignore_ascii_case("xlsx"))
{
return Ok(OutputFormat::Xlsx);
}
if output
.and_then(|path| path.extension())
.map(|extension| extension.to_string_lossy())
.is_some_and(|extension| extension.eq_ignore_ascii_case("csv"))
{
return Ok(OutputFormat::Csv);
}
if output
.and_then(|path| path.extension())
.map(|extension| extension.to_string_lossy())
.is_some_and(|extension| extension.eq_ignore_ascii_case("jsonl"))
{
return Ok(OutputFormat::Jsonl);
}
if output
.and_then(|path| path.extension())
.map(|extension| extension.to_string_lossy())
.is_some_and(|extension| {
extension.eq_ignore_ascii_case("md") || extension.eq_ignore_ascii_case("markdown")
})
{
return Ok(OutputFormat::Markdown);
}
if output
.and_then(|path| path.extension())
.map(|extension| extension.to_string_lossy())
.is_some_and(|extension| extension.eq_ignore_ascii_case("xml"))
{
return Ok(OutputFormat::Xml);
}
Ok(OutputFormat::Text)
}
#[cfg(test)]
mod tests {
use std::path::Path;
use crate::cli::OutputFormat;
use super::resolve_output_format;
#[test]
fn infers_output_format_from_extension() {
assert!(matches!(
resolve_output_format(Some(Path::new("result.xlsx")), None).expect("xlsx format"),
OutputFormat::Xlsx
));
assert!(matches!(
resolve_output_format(Some(Path::new("result.csv")), None).expect("csv format"),
OutputFormat::Csv
));
assert!(matches!(
resolve_output_format(Some(Path::new("result.jsonl")), None).expect("jsonl format"),
OutputFormat::Jsonl
));
assert!(matches!(
resolve_output_format(Some(Path::new("result.md")), None).expect("md format"),
OutputFormat::Markdown
));
assert!(matches!(
resolve_output_format(Some(Path::new("result.txt")), None).expect("text format"),
OutputFormat::Text
));
}
}