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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use std::path::{self, PathBuf, Path};
use clap::{Arg, AppSettings};
use std::borrow::Cow;
use mime::Mime;
use std::fs;
#[cfg(target_os="windows")]
static PATH_LIST_SEPARATOR: char = ';';
#[cfg(not(target_os="windows"))]
static PATH_LIST_SEPARATOR: char = ':';
#[cfg(target_os="windows")]
static ALTERNATIVES_TRANSFORMATIONS_ARG: &str = "-t --transform... [FROM;TO;HOW] 'Transfrom FROM mime-type to an alternative TO mime-type by running HOW'";
#[cfg(not(target_os="windows"))]
static ALTERNATIVES_TRANSFORMATIONS_ARG: &str = "-t --transform... [FROM:TO:HOW] 'Transfrom FROM mime-type to an alternative TO mime-type by running HOW'";
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Verbosity {
None,
Human,
Debug,
}
impl From<u64> for Verbosity {
fn from(n: u64) -> Verbosity {
match n {
0 => Verbosity::None,
1 => Verbosity::Human,
_ => Verbosity::Debug,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Options {
pub maildir: (Cow<'static, str>, PathBuf),
pub feed: (Cow<'static, str>, Option<PathBuf>),
pub verbosity: Verbosity,
pub alternatives_transformations: Vec<(Mime, Mime, String)>,
pub mime_override: Option<Mime>,
}
impl Options {
pub fn parse() -> Options {
#[allow(deprecated)]
let matches = app_from_crate!("\n")
.setting(AppSettings::ColoredHelp)
.arg(Arg::from_usage("[MAILDIR] 'Where to write to the mails to. Default: .'").validator(|s| Options::parse_maildir_path(&s).map(|_| ())))
.arg(Arg::from_usage("[FEED] 'Where to read the feed from. Default: stdin'").validator(|s| Options::parse_feed_path(&s).map(|_| ())))
.arg(Arg::from_usage("-v --verbose... 'Print what's happening to stdout'"))
.arg(Arg::from_usage(ALTERNATIVES_TRANSFORMATIONS_ARG)
.validator(|s| Options::parse_alternatives_transformations(&s).map(|_| ()))
.number_of_values(1))
.arg(Arg::from_usage("-f --force [MIME] 'Type to force content to'").validator(|s| Options::parse_mime_override(&s).map(|_| ())))
.get_matches();
Options {
maildir: match matches.value_of("MAILDIR") {
Some(maildir) => {
({
let mut md = maildir.to_string();
if !md.ends_with(path::is_separator) {
md.push('/');
}
md.into()
},
Options::parse_maildir_path(maildir).expect("Race between validation and parse"))
}
None => ("./".into(), PathBuf::new()),
},
feed: match matches.value_of("FEED") {
Some(feed) => {
match Options::parse_feed_path(feed).expect("Race between validation and parse") {
Some(feed_path) => (feed.to_string().into(), Some(feed_path)),
None => ("<stdin>".into(), None),
}
}
None => ("<stdin>".into(), None),
},
verbosity: matches.occurrences_of("verbose").into(),
alternatives_transformations: matches.values_of("transform")
.into_iter()
.flatten()
.map(Options::parse_alternatives_transformations)
.map(|r| r.expect("Race between validation and parse"))
.collect(),
mime_override: matches.value_of("force").map(Options::parse_mime_override).map(|m| m.expect("Race between validation and parse")),
}
}
fn parse_maildir_path(s: &str) -> Result<PathBuf, String> {
let full_path: Vec<_> = Path::new(s).components().collect();
match full_path.len() {
0 => Err(format!("Path to maildir \"{}\" empty", s)),
1 => Ok(full_path.iter().collect()),
_ => {
let parent_dir: PathBuf = full_path.iter().take(full_path.len() - 1).collect();
let mut path = fs::canonicalize(parent_dir).map_err(|_| format!("Parent to maildir \"{}\" doesn't exist", s))?;
path.push(full_path[full_path.len() - 1]);
Ok(match path.canonicalize() {
Ok(canon_path) => canon_path,
Err(_) => path,
})
}
}
}
fn parse_feed_path(s: &str) -> Result<Option<PathBuf>, String> {
if s == "-" {
return Ok(None);
}
fs::canonicalize(&s).map_err(|_| format!("Feed file \"{}\" doesn't exist", s)).and_then(|f| if f.is_file() {
Ok(Some(f))
} else {
Err(format!("Feed file \"{}\" ({}) not a file", s, f.display()))
})
}
fn parse_alternatives_transformations(s: &str) -> Result<(Mime, Mime, String), String> {
let mut itr = s.split(PATH_LIST_SEPARATOR);
match (itr.next(), itr.next(), itr.next(), itr.next()) {
(Some(from), Some(to), Some(how), None) => {
let from = from.parse().map_err(|e| format!("Transformation triple \"{}\"'s FROM not a mime-type: {}", s, e))?;
let to = to.parse().map_err(|e| format!("Transformation triple \"{}\"'s to not a mime-type: {}", s, e))?;
Ok((from, to, how.to_string()))
}
(_, _, _, Some(_)) => Err(format!("Transformation triple \"{}\" has four components", s)),
(_, Some(_), _, _) => Err(format!("Transformation triple \"{}\" has two components", s)),
(Some(_), _, _, _) => Err(format!("Transformation triple \"{}\" has one component", s)),
(_, _, _, _) => Err(format!("Transformation triple \"{}\" has no components", s)),
}
}
fn parse_mime_override(s: &str) -> Result<Mime, String> {
s.parse().map_err(|e| format!("Type override \"{}\" not a mime-type: {}", s, e))
}
}