cmd_pandoc/
lib.rs

1use std::convert;
2use std::env;
3//use std::error::Error as ErrorTrait;
4use std::fs;
5use std::io;
6use std::ops::Deref;
7use std::process::Command;
8use std::path::{Path, PathBuf};
9use std::result;
10
11#[derive(Debug)]
12pub enum Error {
13    IoError(io::Error),
14    IoErrorContext(io::Error, ErrorContext),
15}
16
17#[derive(Debug)]
18pub enum ErrorContext {
19    ReadDir(PathBuf),
20}
21
22#[derive(Copy, Clone, Debug)]
23pub enum TrackChanges { Accept, Reject, All }
24
25impl TrackChanges {
26    fn display(&self) -> &'static str {
27        match *self {
28            TrackChanges::Accept => "accept",
29            TrackChanges::Reject => "reject",
30            TrackChanges::All    => "all",
31        }
32    }
33}
34
35#[derive(Copy, Clone, Debug)]
36pub enum EmailObfuscation { None, Javascript, References }
37
38impl EmailObfuscation {
39    fn display(&self) -> &'static str {
40        match *self {
41            EmailObfuscation::None => "none",
42            EmailObfuscation::Javascript => "javascript",
43            EmailObfuscation::References => "references",
44        }
45    }
46}
47
48pub type URL = str;
49
50#[derive(Clone, Debug)]
51pub enum PandocOption<'a> {
52    To(OutputFormatExt),                // -t FORMAT  --to=FORMAT
53    DataDir(&'a Path),                  // --data-dir=DIRECTORY
54    Strict,                             // --strict
55    ParseRaw,                           // -R --parse-raw
56    Smart,                              // -S --smart
57    OldDashes,                          // --old-dashes
58    BaseHeaderLevel(u32),               // --base-header-level=NUMBER
59    IndentedCodeClasses(&'a str),       // --indented-code-classes=STRING
60    Filter(&'a Path),                   // -F PROGRAM --filter=PROGRAM
61    Normalize,                          // --normalize
62    PreserveTabs,                       // -p --preserve-tabs
63    TabStop(u32),                       // --tab-stop=NUMBER
64    TrackChanges(TrackChanges),         // --track-changes=accept|reject|all
65    ExtractMedia(&'a Path),             // --extract-media=PATH
66    Standalone,                         // -s --standalone
67    Template(&'a Path),                 // --template=FILENAME
68    Meta(&'a str, Option<&'a str>),     // -M KEY[:VALUE] --metadata=KEY[:VALUE]
69    Var(&'a str, Option<&'a str>),      // -V KEY[:VALUE] --variable=KEY[:VALUE]
70    PrintDefaultTemplate(&'a str),      // -D FORMAT --print-default-template=FORMAT
71    PrintDefaultDataFile(&'a Path),     // --print-default-data-file=FILE
72    NoWrap,                             // --no-wrap
73    Columns(u32),                       // --columns=NUMBER
74    TableOfContents,                    // --toc, --table-of-contents
75    TableOfContentsDepth(u32),          // --toc-depth=NUMBER
76    NoHighlight,                        // --no-highlight
77    HighlightStyle(&'a str),            // --highlight-style=STYLE
78    IncludeInHeader(&'a Path),          // -H FILENAME --include-in-header=FILENAME
79    IncludeBeforeBody(&'a Path),        // -B FILENAME --include-before-body=FILENAME
80    IncludeAfterBody(&'a Path),         // -A FILENAME --include-after-body=FILENAME
81    SelfContained,                      // --self-contained
82    Offline,                            // --offline
83    Html5,                              // -5 --html5
84    HtmlQTags,                          // --html-q-tags
85    Ascii,                              // --ascii
86    ReferenceLinks,                     // --reference-links
87    AtxHeaders,                         // --atx-headers
88    Chapters,                           // --chapters
89    NumberSections,                     // -N --number-sections
90    NumberOffset(&'a [u32]),            // --number-offset=NUMBERS
91    NoTexLigatures,                     // --no-tex-ligatures
92    Listings,                           // --listings
93    Incremental,                        // -i --incremental
94    SlideLevel(u32),                    // --slide-level=NUMBER
95    SectionDivs,                        // --section-divs
96    DefaultImageExtension(&'a str),     // --default-image-extension=extension
97    EmailObfuscation(EmailObfuscation), // --email-obfuscation=none|javascript|references
98    IdPrefix(&'a str),                  // --id-prefix=STRING
99    TitlePrefix(&'a str),               // -T STRING --title-prefix=STRING
100    Css(&'a URL),                       // -c URL --css=URL
101    ReferenceOdt(&'a Path),             // --reference-odt=FILENAME
102    ReferenceDocx(&'a Path),            // --reference-docx=FILENAME
103    EpubStylesheet(&'a Path),           // --epub-stylesheet=FILENAME
104    EpubCoverImage(&'a Path),           // --epub-cover-image=FILENAME
105    EpubMetadata(&'a Path),             // --epub-metadata=FILENAME
106    EpubEmbedFont(&'a Path),            // --epub-embed-font=FILE
107    EpubChapterLevel(u32),              // --epub-chapter-level=NUMBER
108    LatexEngine(&'a Path),              // --latex-engine=PROGRAM
109    LatexEngineOpt(&'a str),            // --latex-engine-opt=STRING
110    Bibliography(&'a Path),             // --bibliography=FILE
111    Csl(&'a Path),                      // --csl=FILE
112    CitationAbbreviations(&'a Path),    // --citation-abbreviations=FILE
113    Natbib,                             // --natbib
114    Biblatex,                           // --biblatex
115    LatexMathML(Option<&'a URL>),       // -m[URL] --latexmathml[=URL], --asciimathml[=URL]
116    AsciiMathML(Option<&'a URL>),       // --asciimathml[=URL]
117    MathML(Option<&'a URL>),            // --mathml[=URL]
118    MimeTex(Option<&'a URL>),           // --mimetex[=URL]
119    WebTex(Option<&'a URL>),            // --webtex[=URL]
120    JsMath(Option<&'a URL>),            // --jsmath[=URL]
121    MathJax(Option<&'a URL>),           // --mathjax[=URL]
122    Katex(Option<&'a URL>),             // --katex[=URL]
123    KatexStylesheet(&'a URL),   // --katex-stylesheet=URL
124    GladTex,                            // -gladtex
125    Trace,                              // --trace
126    DumpArgs,                           // --dump-args
127    IgnoreArgs,                         // --ignore-args
128    Verbose,                            // --verbose
129}
130
131#[derive(Copy, Clone, Debug)]
132#[allow(non_camel_case_types)]
133pub enum OutputFormat {
134    asciidoc, beamer, commonmark, context, docbook, docx, dokuwiki,
135    dzslides, epub, epub3, fb2, haddock, html, html5, icml, json,
136    latex, man, markdown, markdown_github, markdown_mmd,
137    markdown_phpextra, markdown_strict, mediawiki, native, odt,
138    opendocument, opml, org, plain, revealjs, rst, rtf, s5,
139    slideous, slidy, texinfo, textile
140}
141
142#[derive(Clone, Debug)]
143pub enum OutputFormatExt {
144    Fmt(OutputFormat),
145    FmtExt(OutputFormat, Vec<String>),
146}
147
148impl OutputFormatExt {
149    fn render(&self) -> String {
150        match *self {
151            OutputFormatExt::Fmt(ref s) => s.to_string(),
152            OutputFormatExt::FmtExt(ref s, ref ext) => {
153                let mut s = s.to_string();
154                for e in ext {
155                    s.push_str("+");
156                    s.push_str(e);
157                }
158                s
159            }
160        }
161    }
162}
163
164macro_rules! cases {
165    ($x:expr => $($id:ident),*) => {
166        match $x {
167            $($id => stringify!($id)),*
168        }
169    }
170}
171
172impl Deref for OutputFormat {
173    type Target = str;
174
175    fn deref(&self) -> &str {
176        use OutputFormat::*;
177        cases!(*self => asciidoc, beamer, commonmark, context, docbook, docx, dokuwiki,
178               dzslides, epub, epub3, fb2, haddock, html, html5, icml, json,
179               latex, man, markdown, markdown_github, markdown_mmd,
180               markdown_phpextra, markdown_strict, mediawiki, native, odt,
181               opendocument, opml, org, plain, revealjs, rst, rtf, s5,
182               slideous, slidy, texinfo, textile)
183    }
184}
185
186impl<'a> PandocOption<'a> {
187    fn apply<'c>(&self, pandoc: &'c mut Command) -> &'c mut Command {
188        use PandocOption::*;
189        match *self {
190
191            NumberOffset(nums)       => {
192                let nums = nums.iter()
193                    .fold(String::new(),
194                          |b, n| {
195                              if b.len() == 0 {
196                                  format!("{}", n)
197                              } else {
198                                  format!("{}, {}", b, n)
199                              }
200                          });
201                pandoc.args(&[&format!("--number-offset={}", nums)])
202            }
203
204            To(ref f)                => pandoc.args(&["-t", &f.render()]),
205            DataDir(dir)             => pandoc.args(&[&format!("--data-dir={}", dir.display())]),
206            Strict                   => pandoc.args(&["--strict"]),
207            ParseRaw                 => pandoc.args(&["--parse-raw"]),
208            Smart                    => pandoc.args(&["--smart"]),
209            OldDashes                => pandoc.args(&["--old-dashes"]),
210            BaseHeaderLevel(n)       => pandoc.args(&[&format!("--base-header-level={}", n)]),
211            IndentedCodeClasses(s)   => pandoc.args(&[&format!("--indented-code-classes={}", s)]),
212            Filter(program)          => pandoc.args(&[&format!("--filter={}", program.display())]),
213            Normalize                => pandoc.args(&["--normalize"]),
214            PreserveTabs             => pandoc.args(&["--preserve-tabs"]),
215            TabStop(n)               => pandoc.args(&[&format!("--tab-stop={}", n)]),
216            TrackChanges(ref v)      => pandoc.args(&[&format!("--track-changes={}", v.display())]),
217            ExtractMedia(p)          => pandoc.args(&[&format!("--extract-media={}", p.display())]),
218            Standalone               => pandoc.args(&["--standalone"]),
219            Template(p)              => pandoc.args(&[&format!("--template={}", p.display())]),
220            Meta(k, Some(v))         => pandoc.args(&["-M", &format!("{}:{}", k, v)]),
221            Meta(k, None)            => pandoc.args(&["-M", k]),
222            Var(k, Some(v))          => pandoc.args(&["-V", &format!("{}:{}", k, v)]),
223            Var(k, None)             => pandoc.args(&["-V", k]),
224            PrintDefaultTemplate(f)  => pandoc.args(&[&format!("--print-default-template={}", f)]),
225            PrintDefaultDataFile(f)  => pandoc.args(&[&format!("--print-default-data-file={}", f.display())]),
226            NoWrap                   => pandoc.args(&["--no-wrap"]),
227            Columns(n)               => pandoc.args(&[&format!("--columns={}", n)]),
228            TableOfContents          => pandoc.args(&["--table-of-contents"]),
229            TableOfContentsDepth(d)  => pandoc.args(&[&format!("--toc-depth={}", d)]),
230            NoHighlight              => pandoc.args(&["--no-highlight"]),
231            HighlightStyle(s)        => pandoc.args(&[&format!("--highlight-style={}", s)]),
232            IncludeInHeader(p)       => pandoc.args(&[&format!("--include-in-header={}", p.display())]),
233            IncludeBeforeBody(p)     => pandoc.args(&[&format!("--include-before-body={}", p.display())]),
234            IncludeAfterBody(p)      => pandoc.args(&[&format!("--include-after-body={}", p.display())]),
235            SelfContained            => pandoc.args(&["--self-contained"]),
236            Offline                  => pandoc.args(&["--offline"]),
237            Html5                    => pandoc.args(&["--html5"]),
238            HtmlQTags                => pandoc.args(&["--html-q-tags"]),
239            Ascii                    => pandoc.args(&["--ascii"]),
240            ReferenceLinks           => pandoc.args(&["--reference-links"]),
241            AtxHeaders               => pandoc.args(&["--atx-headers"]),
242            Chapters                 => pandoc.args(&["--chapters"]),
243            NumberSections           => pandoc.args(&["--number-sections"]),
244            NoTexLigatures           => pandoc.args(&["--no-tex-ligatures"]),
245            Listings                 => pandoc.args(&["--listings"]),
246            Incremental              => pandoc.args(&["--incremental"]),
247            SlideLevel(n)            => pandoc.args(&[format!("--slide-level={}", n)]),
248            SectionDivs              => pandoc.args(&["--section-divs"]),
249            DefaultImageExtension(s) => pandoc.args(&[format!("--default-image-extension={}", s)]),
250            EmailObfuscation(o)      => pandoc.args(&[format!("--email-obfuscation={}", o.display())]),
251            IdPrefix(s)              => pandoc.args(&[format!("--id-prefix={}", s)]),
252            TitlePrefix(s)           => pandoc.args(&[format!("--title-prefix={}", s)]),
253            Css(url)                 => pandoc.args(&[format!("--css={}", url)]),
254            ReferenceOdt(file)       => pandoc.args(&[format!("--reference-odt={}", file.display())]),
255            ReferenceDocx(file)      => pandoc.args(&[&format!("--reference-docx={}", file.display())]),
256            EpubStylesheet(file)     => pandoc.args(&[&format!("--epub-stylesheet={}", file.display())]),
257            EpubCoverImage(file)     => pandoc.args(&[&format!("--epub-cover-image={}", file.display())]),
258            EpubMetadata(file)       => pandoc.args(&[&format!("--epub-metadata={}", file.display())]),
259            EpubEmbedFont(file)      => pandoc.args(&[&format!("--epub-embed-font={}", file.display())]),
260            EpubChapterLevel(num)    => pandoc.args(&[&format!("--epub-chapter-level={}", num)]),
261            LatexEngine(program)     => pandoc.args(&[&format!("--latex-engine={}", program.display())]),
262            LatexEngineOpt(s)        => pandoc.args(&[&format!("--latex-engine-opt={}", s)]),
263            Bibliography(file)       => pandoc.args(&[&format!("--bibliography={}", file.display())]),
264            Csl(file)                => pandoc.args(&[&format!("--csl={}", file.display())]),
265            CitationAbbreviations(f) => pandoc.args(&[&format!("--citation-abbreviations={}", f.display())]),
266            Natbib                   => pandoc.args(&["--natbib"]),
267            Biblatex                 => pandoc.args(&["--biblatex"]),
268            LatexMathML(Some(url))   => pandoc.args(&[&format!("--latexmathml={}", url)]),
269            AsciiMathML(Some(url))   => pandoc.args(&[&format!("--asciimathml={}", url)]),
270            MathML(Some(url))        => pandoc.args(&[&format!("--mathml={}", url)]),
271            MimeTex(Some(url))       => pandoc.args(&[&format!("--mimetex={}", url)]),
272            WebTex(Some(url))        => pandoc.args(&[&format!("--webtex={}", url)]),
273            JsMath(Some(url))        => pandoc.args(&[&format!("--jsmath={}", url)]),
274            MathJax(Some(url))       => pandoc.args(&[&format!("--mathjax={}", url)]),
275            Katex(Some(url))         => pandoc.args(&[&format!("--katex={}", url)]),
276            LatexMathML(None)        => pandoc.args(&["--latexmathml"]),
277            AsciiMathML(None)        => pandoc.args(&["--asciimathml"]),
278            MathML(None)             => pandoc.args(&["--mathml"]),
279            MimeTex(None)            => pandoc.args(&["--mimetex["]),
280            WebTex(None)             => pandoc.args(&["--webtex["]),
281            JsMath(None)             => pandoc.args(&["--jsmath["]),
282            MathJax(None)            => pandoc.args(&["--mathjax["]),
283            Katex(None)              => pandoc.args(&["--katex["]),
284            KatexStylesheet(url)     => pandoc.args(&[&format!("--katex-stylesheet={}", url)]),
285            GladTex                  => pandoc.args(&["--gladtex"]),
286            Trace                    => pandoc.args(&["--trace"]),
287            DumpArgs                 => pandoc.args(&["--dump-args"]),
288            IgnoreArgs               => pandoc.args(&["--ignore-args"]),
289            Verbose                  => pandoc.args(&["--verbose"]),
290        }
291    }
292}
293
294impl convert::From<io::Error> for Error {
295    fn from(e: io::Error) -> Self {
296        Error::IoError(e)
297    }
298}
299
300pub type Result<X> = result::Result<X, Error>;
301
302fn read_dir<P: AsRef<Path>>(path: P) -> Result<fs::ReadDir> {
303    let path = path.as_ref();
304    fs::read_dir(path).map_err(|e| {
305        Error::IoErrorContext(e, ErrorContext::ReadDir(path.to_path_buf()))
306    })
307}
308
309pub fn run_pandoc(src_dir: &str, target_name: &str, opts: &[PandocOption]) -> Result<()> {
310    let src_dir_path = &format!("src/{}", src_dir);
311    let mut src_paths = Vec::new();
312    for entry in try!(read_dir(src_dir_path)) {
313        let entry = try!(entry);
314        if is_skipped_md(&entry) { continue; }
315        if let Some("md") = entry.path().extension().and_then(|p|p.to_str()) {
316            src_paths.push(entry.path());
317        }
318    }
319    let tgt_path = &format!("{}.html", target_name);
320    let mut pandoc = Command::new("pandoc");
321    for o in opts {
322        o.apply(&mut pandoc);
323    }
324    pandoc.args(&["-o", tgt_path]);
325    src_paths.sort();
326    for p in src_paths {
327        pandoc.arg(p);
328    }
329
330    let command = format!("{:?}", pandoc);
331    println!("command: {}", command);
332    match pandoc.output() {
333        Ok(ref output) if output.status.success() => {}
334        Ok(ref output) => {
335            panic!("something went wrong running pandoc; \
336                    command: {} current_dir: {} exit status: {:?} stdout: {} stderr: {}",
337                   command,
338                   env::current_dir().unwrap().display(),
339                   output.status.code(),
340                   String::from_utf8_lossy(&output.stdout),
341                   String::from_utf8_lossy(&output.stderr),
342                   );
343        }
344        Err(e) => {
345            panic!("something went wrong running pandoc; \
346                    command: {} current_dir: {} err: {} PATH: {:?}",
347                   command,
348                   env::current_dir().unwrap().display(),
349                   e,
350                   env::var_os("PATH"));
351        }
352    }
353
354    Ok(())
355}
356
357fn is_skipped_md(entry: &fs::DirEntry) -> bool {
358    {
359        match entry.path().file_name().and_then(|p|p.to_str()) {
360            Some("mod.md") | Some("lib.md") => true,
361            _ => false,
362        }
363    }
364}
365
366
367#[test]
368fn it_works() {
369}