summaryrefslogtreecommitdiff
path: root/src/bin/sp-docgen.rs
blob: 61522cedc1533c830f853d03c65f3886460c7844 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use chrono::{Local, TimeZone};
use std::ffi::OsString;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::time::UNIX_EPOCH;
use structopt::StructOpt;

use anyhow::Result;

use subplot::{get_basedir_from, resource, Document, Style};

// Define the command line arguments.
#[derive(Debug, StructOpt)]
#[structopt(name = "docgen", about = "Subplot document generator.")]
struct Opt {
    // One or more input filename.
    #[structopt(parse(from_os_str))]
    filenames: Vec<PathBuf>,

    // Set output file name.
    #[structopt(name = "FILE", long = "--output", short = "-o", parse(from_os_str))]
    output: PathBuf,

    // Set date.
    #[structopt(name = "DATE", long = "--date")]
    date: Option<String>,

    #[structopt(flatten)]
    resources: resource::ResourceOpts,
}

fn main() -> Result<()> {
    let opt = Opt::from_args();
    opt.resources.handle();
    let mut pandoc = pandoc::new();

    let first_file = &opt.filenames[0];

    let mut style = Style::default();
    if opt.output.extension() == Some(&OsString::from("pdf")) {
        style.typeset_links_as_notes();
    }

    let basedir = get_basedir_from(first_file)?;
    let mut doc = Document::from_file(&basedir, &first_file, style)?;
    doc.lint()?;
    if !doc.check_named_files_exist()? {
        eprintln!("Continuing despite warnings");
    }

    // Metadata date from command line or file mtime. However, we
    // can't set it directly, since we don't want to override the date
    // in the actual document, if given, so we only set
    // user-provided-date. Our parsing code will use that if date is
    // not document metadata.
    let date = if let Some(date) = opt.date {
        date
    } else if let Some(date) = doc.meta().date() {
        date.to_string()
    } else {
        mtime_formatted(mtime(first_file)?)
    };
    pandoc.add_option(pandoc::PandocOption::Meta("date".to_string(), Some(date)));
    pandoc.add_option(pandoc::PandocOption::TableOfContents);
    pandoc.add_option(pandoc::PandocOption::Standalone);
    pandoc.add_option(pandoc::PandocOption::NumberSections);

    if need_output(&mut doc, &opt.output) {
        doc.typeset();
        pandoc.set_input_format(pandoc::InputFormat::Json, vec![]);
        pandoc.set_input(pandoc::InputKind::Pipe(doc.ast()?));
        pandoc.set_output(pandoc::OutputKind::File(opt.output.clone()));
        pandoc.execute()?;
    }

    Ok(())
}

fn mtime(filename: &Path) -> Result<(u64, u32)> {
    let mtime = fs::metadata(filename)?.modified()?;
    let mtime = mtime.duration_since(UNIX_EPOCH)?;
    Ok((mtime.as_secs(), mtime.subsec_nanos()))
}

fn mtime_formatted(mtime: (u64, u32)) -> String {
    let secs: i64 = format!("{}", mtime.0).parse().unwrap_or(0);
    let dt = Local.timestamp(secs, mtime.1);
    dt.format("%Y-%m-%d %H:%M").to_string()
}

fn need_output(doc: &mut subplot::Document, output: &Path) -> bool {
    let output = match mtime(output) {
        Err(_) => return true,
        Ok(ts) => ts,
    };

    for filename in doc.sources() {
        let source = match mtime(&filename) {
            Err(_) => return true,
            Ok(ts) => ts,
        };
        if source >= output {
            return true;
        }
    }
    false
}