1use crate::metadata::Metadata;
3
4use std::env;
5use std::fmt;
6use std::io::{self, Error as IOError, ErrorKind};
7use std::path::PathBuf;
8use std::process::{Command, Output};
9
10const PANDOC_CMD: &str = "pandoc";
13
14const PANDOC_ENV: &str = "PANDOC_CMD";
17
18pub struct DebugInfo {
21 input: String,
23 template: Option<String>,
25 output: String,
27 err: String,
29 tried_extracting_header: bool,
32}
33
34impl fmt::Display for DebugInfo {
35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36 match self.tried_extracting_header {
37 true => write!(
38 f,
39 "pandoc failed to extract header metadata from \"{}\" {}",
40 self.input, self.err,
41 ),
42
43 false => write!(
44 f,
45 "pandoc failed to convert \"{}\" to \"{}\" with template \"{}\" {}",
46 self.input,
47 self.output,
48 match self.template {
49 Some(ref x) => String::from(x),
50 None => String::from("<undefined>"),
51 },
52 self.err,
53 ),
54 }
55 }
56}
57
58pub enum PandocError<'a> {
60 NotFound(String),
63 RelativePath(PathBuf, &'a str),
68 StringFromUtf8,
70 ExecutionFailed(DebugInfo),
72 CallFailed(IOError),
75}
76
77impl fmt::Display for PandocError<'_> {
78 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 match &self {
80 PandocError::NotFound(executable) => match executable == &PANDOC_CMD {
81 true => write!(
82 f,
83 "couldn't find \"pandoc\" on your system, use the env \"{}\" to use an non default executable name",
84 PANDOC_ENV
85 ),
86 false => write!(
87 f,
88 "couldn't find pandoc with the executable name \"{}\" use env \"{}\" to specify otherwise",
89 executable,
90 PANDOC_ENV
91 ),
92 },
93 PandocError::RelativePath(path, purpose) => write!(
94 f,
95 "internal error, pandoc module was called with an relative {} path {}, only absolute paths allowed",
96 purpose,
97 path.display()
98 ),
99 PandocError::StringFromUtf8 => write!(
100 f,
101 "couldn't convert standard output (stdout) from pandoc"
102 ),
103 PandocError::ExecutionFailed(info) => write!(
104 f,
105 "{}",
106 info
107 ),
108 PandocError::CallFailed(err) => write!(
109 f,
110 "couldn't call pandoc {}",
111 err,
112 ),
113 }
114 }
115}
116
117pub struct Pandoc(String);
120
121impl<'a> Pandoc {
122 pub fn new() -> Self {
126 Self(match env::var(PANDOC_ENV) {
127 Ok(x) => x,
128 Err(_) => String::from(PANDOC_CMD),
129 })
130 }
131
132 pub fn convert_with_template_to_str(
135 &self,
136 input: &PathBuf,
137 template: PathBuf,
138 ) -> Result<String, PandocError<'a>> {
139 check_path(input.clone(), "input")?;
140 check_path(template.clone(), "template")?;
141 debug!(
142 "input: {}, template: {}",
143 input.display(),
144 template.display()
145 );
146
147 let mut cmd = Command::new(self.0.clone());
148 cmd.arg("--template").arg(template).arg(&input);
149
150 Pandoc::output_to_result(
151 cmd.output(),
152 self.0.clone(),
153 true,
154 String::from(input.to_str().unwrap()),
155 String::new(),
156 None,
157 )
158 }
159
160 pub fn convert_with_metadata_to_pdf(
166 &self,
167 input: &PathBuf,
168 metadata: Metadata,
169 output: &PathBuf,
170 resource_path: Option<&PathBuf>,
171 ) -> Result<(), PandocError<'a>> {
172 let mut cmd = Command::new(self.0.clone());
173 cmd.arg(&input)
174 .arg("--pdf-engine")
175 .arg(metadata.engine)
176 .arg("--wrap=preserve");
177 if let Some(ref template) = metadata.template {
178 cmd.arg("--template").arg(template);
179 }
180 if let Some(options) = metadata.pandoc_options {
181 cmd.args(options);
182 }
183 if let Some(_bibliography) = metadata.bibliography {
184 cmd.arg("--citeproc");
185 }
186 if let Some(path) = resource_path {
187 cmd.arg("--resource-path").arg(path);
188 }
189 cmd.arg("-o").arg(&output);
190 match Pandoc::output_to_result(
191 cmd.output(),
192 self.0.clone(),
193 false,
194 String::from(input.to_str().unwrap()),
195 String::from(output.to_str().unwrap()),
196 match metadata.template {
197 Some(x) => Some(String::from(x.to_str().unwrap())),
198 None => None,
199 },
200 ) {
201 Ok(_) => Ok(()),
202 Err(e) => Err(e),
203 }
204 }
205
206 pub fn convert_with_metadata_to_office(
212 &self,
213 input: &PathBuf,
214 metadata: Metadata,
215 output: &PathBuf,
216 resource_path: Option<&PathBuf>,
217 ) -> Result<(), PandocError<'a>> {
218 let mut cmd = Command::new(self.0.clone());
219 cmd.arg(&input);
220 if let Some(ref reference) = metadata.reference {
221 cmd.arg("--reference-doc").arg(reference);
222 }
223 if let Some(options) = metadata.pandoc_options {
224 cmd.args(options);
225 }
226 if let Some(_bibliography) = metadata.bibliography {
227 cmd.arg("--citeproc");
228 }
229 if let Some(path) = resource_path {
230 cmd.arg("--resource-path").arg(path);
231 }
232 cmd.arg("-o").arg(&output);
233 match Pandoc::output_to_result(
234 cmd.output(),
235 self.0.clone(),
236 false,
237 String::from(input.to_str().unwrap()),
238 String::from(output.to_str().unwrap()),
239 None,
240 ) {
241 Ok(_) => Ok(()),
242 Err(e) => Err(e),
243 }
244 }
245
246 pub fn convert_with_metadata_to_reveal(
252 &self,
253 input: &PathBuf,
254 metadata: Metadata,
255 output: &PathBuf,
256 resource_path: Option<&PathBuf>,
257 ) -> Result<(), PandocError<'a>> {
258 let mut cmd = Command::new(self.0.clone());
259 cmd.arg(&input)
260 .arg("-t")
261 .arg("revealjs")
262 .arg("-s");
263 if let Some(options) = metadata.pandoc_options {
264 cmd.args(options);
265 }
266 if let Some(_bibliography) = metadata.bibliography {
267 cmd.arg("--citeproc");
268 }
269 if let Some(path) = resource_path {
270 cmd.arg("--resource-path").arg(path);
271 }
272 cmd.arg("-o").arg(&output);
273 match Pandoc::output_to_result(
274 cmd.output(),
275 self.0.clone(),
276 false,
277 String::from(input.to_str().unwrap()),
278 String::from(output.to_str().unwrap()),
279 None,
280 ) {
281 Ok(_) => Ok(()),
282 Err(e) => Err(e),
283 }
284 }
285
286
287 fn output_to_result(
289 rsl: io::Result<Output>,
290 pandoc_bin: String,
291 tried_extracting_header: bool,
292 input: String,
293 output: String,
294 temlate: Option<String>,
295 ) -> Result<String, PandocError<'a>> {
296 match rsl {
297 Ok(x) => {
298 if x.status.success() {
299 match String::from_utf8(x.stdout) {
300 Ok(x) => Ok(x),
301 Err(_) => Err(PandocError::StringFromUtf8),
302 }
303 } else {
304 Err(PandocError::ExecutionFailed(DebugInfo {
305 input: input,
306 output: output,
307 template: temlate,
308 err: String::from_utf8(x.stderr).unwrap(),
309 tried_extracting_header: tried_extracting_header,
310 }))
311 }
312 }
313 Err(e) => {
314 if let ErrorKind::NotFound = e.kind() {
315 Err(PandocError::NotFound(pandoc_bin))
316 } else {
317 Err(PandocError::CallFailed(e))
318 }
319 }
320 }
321 }
322}
323
324fn check_path<'a>(path: PathBuf, purpose: &'a str) -> Result<(), PandocError<'a>> {
326 match path.is_absolute() {
327 true => Ok(()),
328 false => Err(PandocError::RelativePath(path, purpose)),
329 }
330}