use crate::metadata::Metadata;
use std::env;
use std::fmt;
use std::io::{self, Error as IOError, ErrorKind};
use std::path::PathBuf;
use std::process::{Command, Output};
const PANDOC_CMD: &str = "pandoc";
const PANDOC_ENV: &str = "PANDOC_CMD";
pub struct DebugInfo {
input: String,
template: Option<String>,
output: String,
err: String,
tried_extracting_header: bool,
}
impl fmt::Display for DebugInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.tried_extracting_header {
true => write!(
f,
"pandoc failed to extract header metadata from \"{}\" {}",
self.input, self.err,
),
false => write!(
f,
"pandoc failed to convert \"{}\" to \"{}\" with template \"{}\" {}",
self.input,
self.output,
match self.template {
Some(ref x) => String::from(x),
None => String::from("<undefined>"),
},
self.err,
),
}
}
}
pub enum PandocError<'a> {
NotFound(String),
RelativePath(PathBuf, &'a str),
StringFromUtf8,
ExecutionFailed(DebugInfo),
CallFailed(IOError),
}
impl fmt::Display for PandocError<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
PandocError::NotFound(executable) => match executable == &PANDOC_CMD {
true => write!(
f,
"couldn't find \"pandoc\" on your system, use the env \"{}\" to use an non default executable name",
PANDOC_ENV
),
false => write!(
f,
"couldn't find pandoc with the executable name \"{}\" use env \"{}\" to specify otherwise",
executable,
PANDOC_ENV
),
},
PandocError::RelativePath(path, purpose) => write!(
f,
"internal error, pandoc module was called with an relative {} path {}, only absolute paths allowed",
purpose,
path.display()
),
PandocError::StringFromUtf8 => write!(
f,
"couldn't convert standard output (stdout) from pandoc"
),
PandocError::ExecutionFailed(info) => write!(
f,
"{}",
info
),
PandocError::CallFailed(err) => write!(
f,
"couldn't call pandoc {}",
err,
),
}
}
}
pub struct Pandoc(String);
impl<'a> Pandoc {
pub fn new() -> Self {
Self(match env::var(PANDOC_ENV) {
Ok(x) => x,
Err(_) => String::from(PANDOC_CMD),
})
}
pub fn convert_with_template_to_str(
&self,
input: &PathBuf,
template: PathBuf,
) -> Result<String, PandocError<'a>> {
check_path(input.clone(), "input")?;
check_path(template.clone(), "template")?;
debug!(
"input: {}, template: {}",
input.display(),
template.display()
);
let mut cmd = Command::new(self.0.clone());
cmd.arg("--template").arg(template).arg(&input);
Pandoc::output_to_result(
cmd.output(),
self.0.clone(),
true,
String::from(input.to_str().unwrap()),
String::new(),
None,
)
}
pub fn convert_with_metadata_to_pdf(
&self,
input: &PathBuf,
metadata: Metadata,
output: &PathBuf,
resource_path: Option<&PathBuf>,
) -> Result<(), PandocError<'a>> {
let mut cmd = Command::new(self.0.clone());
cmd.arg(&input)
.arg("--pdf-engine")
.arg(metadata.engine)
.arg("--wrap=preserve");
if let Some(ref template) = metadata.template {
cmd.arg("--template").arg(template);
}
if let Some(options) = metadata.pandoc_options {
cmd.args(options);
}
if let Some(_bibliography) = metadata.bibliography {
cmd.arg("--citeproc");
}
if let Some(path) = resource_path {
cmd.arg("--resource-path").arg(path);
}
cmd.arg("-o").arg(&output);
match Pandoc::output_to_result(
cmd.output(),
self.0.clone(),
false,
String::from(input.to_str().unwrap()),
String::from(output.to_str().unwrap()),
match metadata.template {
Some(x) => Some(String::from(x.to_str().unwrap())),
None => None,
},
) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
pub fn convert_with_metadata_to_office(
&self,
input: &PathBuf,
metadata: Metadata,
output: &PathBuf,
resource_path: Option<&PathBuf>,
) -> Result<(), PandocError<'a>> {
let mut cmd = Command::new(self.0.clone());
cmd.arg(&input);
if let Some(ref reference) = metadata.reference {
cmd.arg("--reference-doc").arg(reference);
}
if let Some(options) = metadata.pandoc_options {
cmd.args(options);
}
if let Some(_bibliography) = metadata.bibliography {
cmd.arg("--citeproc");
}
if let Some(path) = resource_path {
cmd.arg("--resource-path").arg(path);
}
cmd.arg("-o").arg(&output);
match Pandoc::output_to_result(
cmd.output(),
self.0.clone(),
false,
String::from(input.to_str().unwrap()),
String::from(output.to_str().unwrap()),
None,
) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
pub fn convert_with_metadata_to_reveal(
&self,
input: &PathBuf,
metadata: Metadata,
output: &PathBuf,
resource_path: Option<&PathBuf>,
) -> Result<(), PandocError<'a>> {
let mut cmd = Command::new(self.0.clone());
cmd.arg(&input)
.arg("-t")
.arg("revealjs")
.arg("-s");
if let Some(options) = metadata.pandoc_options {
cmd.args(options);
}
if let Some(_bibliography) = metadata.bibliography {
cmd.arg("--citeproc");
}
if let Some(path) = resource_path {
cmd.arg("--resource-path").arg(path);
}
cmd.arg("-o").arg(&output);
match Pandoc::output_to_result(
cmd.output(),
self.0.clone(),
false,
String::from(input.to_str().unwrap()),
String::from(output.to_str().unwrap()),
None,
) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
fn output_to_result(
rsl: io::Result<Output>,
pandoc_bin: String,
tried_extracting_header: bool,
input: String,
output: String,
temlate: Option<String>,
) -> Result<String, PandocError<'a>> {
match rsl {
Ok(x) => {
if x.status.success() {
match String::from_utf8(x.stdout) {
Ok(x) => Ok(x),
Err(_) => Err(PandocError::StringFromUtf8),
}
} else {
Err(PandocError::ExecutionFailed(DebugInfo {
input: input,
output: output,
template: temlate,
err: String::from_utf8(x.stderr).unwrap(),
tried_extracting_header: tried_extracting_header,
}))
}
}
Err(e) => {
if let ErrorKind::NotFound = e.kind() {
Err(PandocError::NotFound(pandoc_bin))
} else {
Err(PandocError::CallFailed(e))
}
}
}
}
}
fn check_path<'a>(path: PathBuf, purpose: &'a str) -> Result<(), PandocError<'a>> {
match path.is_absolute() {
true => Ok(()),
false => Err(PandocError::RelativePath(path, purpose)),
}
}