about summary refs log tree commit diff
path: root/pkgs/profpatsch/text-letter.rs
blob: 55921adcd12a43b3b7d74ea204a023d014adb3d6 (plain) (blame)
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Reads a toml file on stdin describing a simple letter format,
//! outputs a text formatting of the file, which can be piped to lp
//! and should be viable to use on A4 paper with one of these windowed envelopes.
//! Might depend on the printer.
//!
//! ```
//! cat letter.toml | ./text-letter | lp -h localhost -d printer-name -
//! ```
extern crate mustache;
extern crate toml;
extern crate regex;
extern crate lazy_static;


use std::io::{Read, Write};

struct Letter {
    current_city: String,
    from_address: String,
    to_address: String,
    content: String,
    date: Date
}

struct Date {
    year: String,
    month: String,
    day: String,
}


lazy_static::lazy_static!{
    static ref date_regex: regex::bytes::Regex = regex::bytes::Regex::new(r"^([0-9]{4})-([0-9]{2})-([0-9]{2})$").unwrap();
}

fn parse_date(s: &str) -> Option<Date> {
    date_regex.captures(s.as_bytes()).map(|c| Date {
        year: String::from_utf8(c.get(1).unwrap().as_bytes().to_vec()).unwrap(),
        month: String::from_utf8(c.get(2).unwrap().as_bytes().to_vec()).unwrap(),
        day: String::from_utf8(c.get(3).unwrap().as_bytes().to_vec()).unwrap(),
    })
}

fn parse_letter_toml(input: &str) -> Letter {
    let v : toml::Value = toml::from_str(input).expect("could not parse letter TOML");
    let get = |field: &str| -> &str {
        v.get(field).expect(&format!("field {} is missing in letter TOML", field))
          .as_str().expect(&format!("field {} must be a string", field))
    };
    assert_eq!(get("type"), "letter", "type must be `letter`");
    assert_eq!(get("version"), "0.0.1", "version must be `0.0.1`");

    Letter {
        current_city: get("current-city").to_owned(),
        from_address: get("from-address").trim_right().to_owned(),
        to_address: get("to-address").trim_right().to_owned(),
        content: get("content").trim_right().to_owned(),
        date: parse_date(get("date")).expect("field `date` needs to be a date like yyyy-mm-dd"),
    }
}

fn main() {
    let mut letter = String::new();
    std::io::stdin().read_to_string(&mut letter).expect("stdin has to be an utf-8 string");
    let letter = parse_letter_toml(&letter);
    let from_address_one_line = letter.from_address.replace("\n", ", ");

    fn indent4(s: String) -> String {
        let mut res = String::from("    ");
        res.push_str(&s.replace("\n", "\n    "));
        res
    }
    fn indent2(s: String) -> String {
        let mut res = String::from("  ");
        res.push_str(&s.replace("\n", "\n  "));
        res
    }

    let template = mustache::compile_str(r###"
{{{date}}}









{{{from_address_one_line}}}

{{{an}}}
{{{to_address}}}









{{content}}
"###
    ).expect("the template is malformed");


    let data : std::collections::HashMap<String, mustache::Data> =
        [("an", indent4("An:".to_string())),
         ("from_address_one_line", indent4(from_address_one_line)),
         ("to_address", indent4(letter.to_address)),
         ("content", letter.content),
         ("date", format!(
             "{}, den {}.{}.{}",
             letter.current_city,
             letter.date.day,
             letter.date.month,
             letter.date.year
         ))
        ][..].into_iter().cloned()
        .map(|(k,v)| (k.to_owned(), mustache::Data::String(v))).collect();
    let data = mustache::Data::Map(data);
    let res = template.render_data_to_string(
        &data
    ).expect("could not render template");

    // give a slight margin
    let res = indent2(res);

    std::io::stdout().write_all(&res.as_bytes()).unwrap()
}