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()
}
|