pub mod browser;
use std::ffi::OsStr;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use clap::Args;
use miette::{IntoDiagnostic, WrapErr};
use svugy_model::model::Model;
use svugy_parse::json::JsonPrinter;
use svugy_parse::markdown::MarkdownPrinter;
use crate::failure::Failure;
use browser::DefaultBrowser;
type PrintModel = fn(&Model) -> Result<String, Failure>;
#[derive(Args)]
pub struct OutputTarget {
#[arg(short, long)]
output: Option<PathBuf>,
}
#[derive(Clone)]
pub struct ModelFile {
path: PathBuf,
print_model: PrintModel,
}
impl OutputTarget {
pub fn write(&self, contents: &str) -> Result<(), Failure> {
let Some(output) = &self.output else {
return Self::write_stdout(contents);
};
Self::write_file(output, contents)
}
pub fn open_in_default_browser(&self) -> Result<(), Failure> {
let Some(output) = &self.output else {
return Ok(());
};
let output_path = output.as_os_str();
DefaultBrowser::open(output_path)
}
pub fn write_stdout(contents: &str) -> Result<(), Failure> {
let mut stdout = io::stdout().lock();
let content_bytes = contents.as_bytes();
stdout
.write_all(content_bytes)
.into_diagnostic()
.wrap_err_with(|| "cannot write to stdout")
.map_err(Failure::Io)
}
pub fn write_file(path: &Path, contents: &str) -> Result<(), Failure> {
let output_path = path.display();
fs::write(path, contents)
.into_diagnostic()
.wrap_err_with(|| format!("cannot write {output_path}"))
.map_err(Failure::Io)
}
}
impl ModelFile {
pub fn parse(argument: &str) -> Result<Self, String> {
let path = PathBuf::from(argument);
let extension = path.extension().and_then(OsStr::to_str);
let print_model: PrintModel = match extension {
Some("md") => Self::print_markdown,
Some("json") => Self::print_json,
_unsupported_extension => {
let unsupported_message =
"expected a `.md` diagram or a `.json` model to write".to_owned();
return Err(unsupported_message);
}
};
Ok(Self { path, print_model })
}
pub fn write(&self, model: &Model) -> Result<(), Failure> {
let model_text = (self.print_model)(model)?;
OutputTarget::write_file(&self.path, &model_text)
}
fn print_markdown(model: &Model) -> Result<String, Failure> {
MarkdownPrinter::print(model)
.into_diagnostic()
.map_err(Failure::UnprintableModel)
}
fn print_json(model: &Model) -> Result<String, Failure> {
let model_json = JsonPrinter::print(model);
Ok(model_json)
}
}