use std::{
cell::Cell,
fs::{self, OpenOptions},
io::{self, Write},
path::Path,
};
use serde::Serialize;
use serde_json::json;
use crate::config::{OutputConfig, OutputFormat};
use crate::error::AppError;
pub struct Printer {
config: OutputConfig,
wrote_once: Cell<bool>,
}
impl Printer {
pub fn new(config: OutputConfig) -> Self {
Self {
config,
wrote_once: Cell::new(false),
}
}
pub fn print_banner(&self, app_name: &str, version: &str) -> Result<(), AppError> {
if self.config.format == OutputFormat::Text {
self.with_writer(|writer| {
writeln!(writer, "{app_name} {version}")?;
Ok(())
})?;
}
Ok(())
}
pub fn print_run_ready(&self, app_name: &str, version: &str) -> Result<(), AppError> {
match self.config.format {
OutputFormat::Text => self.with_writer(|writer| {
writeln!(writer, "mode: cli")?;
writeln!(writer, "status: ready")?;
writeln!(
writer,
"next: run `amagi run douyin emoji-list`, `amagi run kuaishou emoji-list`, or start `amagi serve`"
)?;
Ok(())
}),
OutputFormat::Json => self.write_json(&json!({
"kind": "run_ready",
"app": app_name,
"version": version,
"mode": "cli",
"status": "ready",
"next": "run `amagi run douyin emoji-list`, `amagi run kuaishou emoji-list`, or start `amagi serve`"
})),
}
}
pub fn print_server_ready(
&self,
app_name: &str,
version: &str,
bind_addr: &str,
) -> Result<(), AppError> {
let root = format!("http://{bind_addr}/");
let health = format!("http://{bind_addr}/health");
let douyin_emoji = format!("http://{bind_addr}/api/douyin/emoji");
let kuaishou_emoji = format!("http://{bind_addr}/api/kuaishou/emoji");
match self.config.format {
OutputFormat::Text => self.with_writer(|writer| {
writeln!(writer, "mode: server")?;
writeln!(writer, "status: listening")?;
writeln!(writer, "addr: {bind_addr}")?;
writeln!(writer, "root: {root}")?;
writeln!(writer, "health: {health}")?;
writeln!(writer, "douyin_emoji: {douyin_emoji}")?;
writeln!(writer, "kuaishou_emoji: {kuaishou_emoji}")?;
Ok(())
}),
OutputFormat::Json => self.write_json(&json!({
"kind": "server_ready",
"app": app_name,
"version": version,
"mode": "server",
"status": "listening",
"addr": bind_addr,
"root": root,
"health": health,
"douyin_emoji": douyin_emoji,
"kuaishou_emoji": kuaishou_emoji
})),
}
}
pub fn print_payload<T: Serialize>(&self, value: &T) -> Result<(), AppError> {
self.with_writer(|writer| {
match self.config.format {
OutputFormat::Text => serde_json::to_writer_pretty(&mut *writer, value)?,
OutputFormat::Json if self.config.pretty => {
serde_json::to_writer_pretty(&mut *writer, value)?
}
OutputFormat::Json => serde_json::to_writer(&mut *writer, value)?,
}
writeln!(writer)?;
Ok(())
})
}
fn write_json<T: Serialize>(&self, value: &T) -> Result<(), AppError> {
self.with_writer(|writer| {
if self.config.pretty {
serde_json::to_writer_pretty(&mut *writer, value)?;
} else {
serde_json::to_writer(&mut *writer, value)?;
}
writeln!(writer)?;
Ok(())
})
}
fn with_writer<F>(&self, write: F) -> Result<(), AppError>
where
F: FnOnce(&mut dyn Write) -> Result<(), AppError>,
{
match self.config.file.as_deref() {
Some(path) => {
let path = Path::new(path);
if self.config.create_parent_dirs {
if let Some(parent) =
path.parent().filter(|value| !value.as_os_str().is_empty())
{
fs::create_dir_all(parent)?;
}
}
let mut options = OpenOptions::new();
options.create(true).write(true);
if self.config.append || self.wrote_once.get() {
options.append(true);
} else {
options.truncate(true);
}
let mut file = options.open(path)?;
write(&mut file)?;
file.flush()?;
self.wrote_once.set(true);
Ok(())
}
None => {
let stdout = io::stdout();
let mut stdout = stdout.lock();
write(&mut stdout)?;
stdout.flush()?;
Ok(())
}
}
}
}