about summary refs log tree commit diff
path: root/pkgs/profpatsch/text-letter.rs
blob: 1ff3bdf8da46c600758018f5512e5454fb768308 (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
//! 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()
}