use std::process::Command;
use crate::errors::{CoreError, CoreResult};
use super::ViewerBackend;
use super::util::command_on_path;
pub struct HtmlViewer;
fn browser_opener() -> &'static str {
if cfg!(target_os = "macos") {
"open"
} else {
"xdg-open"
}
}
impl ViewerBackend for HtmlViewer {
fn name(&self) -> &str {
"html"
}
fn description(&self) -> &str {
"Open the proposal as HTML in the system browser (requires pandoc)"
}
fn is_available(&self) -> bool {
command_on_path("pandoc") && command_on_path(browser_opener())
}
fn availability_hint(&self) -> Option<String> {
if !command_on_path("pandoc") {
return Some(
"pandoc is required for the HTML viewer. \
Install it from https://pandoc.org/installing.html"
.to_string(),
);
}
let opener = browser_opener();
if !command_on_path(opener) {
return Some(format!(
"'{opener}' is required to open the browser. \
Please install it or open the HTML file manually."
));
}
None
}
fn open(&self, content: &str) -> CoreResult<()> {
if !command_on_path("pandoc") {
return Err(CoreError::not_found(
"pandoc is required for the HTML viewer. \
Install it from https://pandoc.org/installing.html",
));
}
let opener = browser_opener();
if !command_on_path(opener) {
return Err(CoreError::not_found(format!(
"'{opener}' is required to open the browser. \
Please install it or open the HTML file manually.",
)));
}
let mut md_file = tempfile::Builder::new()
.prefix("ito-viewer-")
.suffix(".md")
.tempfile()
.map_err(|e| CoreError::io("creating temporary markdown file", e))?;
std::io::Write::write_all(&mut md_file, content.as_bytes())
.map_err(|e| CoreError::io("writing temporary markdown file", e))?;
let html_path = md_file.path().with_extension("html");
let pandoc_output = Command::new("pandoc")
.arg("--standalone")
.arg("--from=markdown")
.arg("--to=html5")
.arg("-o")
.arg(&html_path)
.arg(md_file.path())
.output()
.map_err(|e| CoreError::io("spawning pandoc", e))?;
if !pandoc_output.status.success() {
return Err(CoreError::process(format!(
"pandoc failed: {}",
String::from_utf8_lossy(&pandoc_output.stderr).trim()
)));
}
let open_output = Command::new(opener)
.arg(&html_path)
.output()
.map_err(|e| CoreError::io(format!("spawning {opener}"), e))?;
if !open_output.status.success() {
return Err(CoreError::process(format!(
"{opener} failed: {}",
String::from_utf8_lossy(&open_output.stderr).trim()
)));
}
Ok(())
}
}
#[cfg(test)]
#[path = "html_tests.rs"]
mod html_tests;