1use std::borrow::ToOwned;
2use std::env;
3use std::error::Error;
4use std::fmt;use std::fs;
5use std::io::{Read,Write};
6use std::io;
7use std::path;
8use std::process;
9
10use atomicwrites::{AtomicFile,AllowOverwrite};
11
12use utils;
13use utils::CustomPathExt;
14use app;
15use editor;
16
17
18#[inline]
19fn get_pwd() -> path::PathBuf {
20 env::current_dir().ok().expect("Failed to get CWD")
21}
22
23#[inline]
24fn get_envvar(key: &str) -> Option<String> {
25 match env::var(key) {
26 Ok(x) => Some(x),
27 Err(env::VarError::NotPresent) => None,
28 Err(env::VarError::NotUnicode(_)) => panic!(format!("{} is not unicode.", key)),
29 }
30}
31
32fn build_index(outfile: &path::Path, dir: &path::Path) -> MainResult<()> {
33 if !dir.is_dir() {
34 return Err(MainError::new("MATES_DIR must be a directory.").into());
35 };
36
37 let af = AtomicFile::new(&outfile, AllowOverwrite);
38 let mut errors = false;
39
40 try!(af.write::<(), io::Error, _>(|outf| {
41 for entry in try!(fs::read_dir(dir)) {
42 let entry = match entry {
43 Ok(x) => x,
44 Err(e) => {
45 println!("Error while listing directory: {}", e);
46 errors = true;
47 continue;
48 }
49 };
50
51 let pathbuf = entry.path();
52
53 if pathbuf.str_extension().unwrap_or("") != "vcf" || !pathbuf.is_file() {
54 continue;
55 };
56
57 let contact = match utils::Contact::from_file(&pathbuf) {
58 Ok(x) => x,
59 Err(e) => {
60 println!("Error while reading {}: {}", pathbuf.display(), e);
61 errors = true;
62 continue
63 }
64 };
65
66 match utils::index_item_from_contact(&contact) {
67 Ok(index_string) => {
68 try!(outf.write_all(index_string.as_bytes()));
69 },
70 Err(e) => {
71 println!("Error while indexing {}: {}", pathbuf.display(), e);
72 errors = true;
73 continue
74 }
75 };
76 };
77 Ok(())
78 }));
79
80 if errors {
81 Err(MainError::new("Several errors happened while generating the index.").into())
82 } else {
83 Ok(())
84 }
85}
86
87pub fn cli_main() {
88 match cli_main_raw() {
89 Err(e) => {
90 writeln!(&mut io::stderr(), "{}", e).unwrap();
91 process::exit(1);
92 },
93 _ => ()
94 };
95}
96
97pub fn cli_main_raw() -> MainResult<()> {
98 let matches = app::app().get_matches();
99
100 let command = matches.subcommand_name().unwrap();
101
102 let config = match Configuration::new() {
103 Ok(x) => x,
104 Err(e) => {
105 return Err(MainError::new(format!("Error while reading configuration: {}", e)).into());
106 }
107 };
108
109 let submatches = matches.subcommand_matches(command).expect("Internal error.");
110
111 match command {
112 "index" => {
113 println!("Rebuilding index file \"{}\"...", config.index_path.display());
114 try!(build_index(&config.index_path, &config.vdir_path));
115 },
116 "mutt-query" => {
117 let query = submatches.value_of("query").unwrap_or("");
118 try!(mutt_query(&config, &query[..]));
119 },
120 "file-query" => {
121 let query = submatches.value_of("query").unwrap_or("");
122 try!(file_query(&config, &query[..]));
123 },
124 "email-query" => {
125 let query = submatches.value_of("query").unwrap_or("");
126 try!(email_query(&config, &query[..]));
127 },
128 "add" => {
129 let stdin = io::stdin();
130 let mut email = String::new();
131 try!(stdin.lock().read_to_string(&mut email));
132 let contact = try!(utils::add_contact_from_email(
133 &config.vdir_path,
134 &email[..]
135 ));
136 println!("{}", contact.path.display());
137
138 let mut index_fp = try!(fs::OpenOptions::new()
139 .append(true)
140 .write(true)
141 .open(&config.index_path));
142
143 let index_entry = try!(utils::index_item_from_contact(&contact));
144 try!(index_fp.write_all(index_entry.as_bytes()));
145 },
146 "edit" => {
147 let query = submatches.value_of("file-or-query").unwrap_or("");
148 try!(edit_contact(&config, &query[..]));
149 },
150 _ => {
151 return Err(MainError::new(format!("Invalid command: {}", command)).into());
152 }
153 };
154 Ok(())
155}
156
157fn edit_contact(config: &Configuration, query: &str) -> MainResult<()> {
158 let results = if get_pwd().join(query).is_file() {
159 vec![path::PathBuf::from(query)]
160 } else {
161 try!(utils::file_query(config, query)).into_iter().collect()
162 };
163
164 if results.len() < 1 {
165 return Err(MainError::new("No such contact.").into());
166 } else if results.len() > 1 {
167 return Err(MainError::new("Ambiguous query.").into());
168 }
169
170 let fpath = &results[0];
171 editor::cli_main(fpath);
172
173 let fcontent = {
174 let mut fcontent = String::new();
175 let mut file = try!(fs::File::open(fpath));
176 try!(file.read_to_string(&mut fcontent));
177 fcontent
178 };
179
180 if (&fcontent[..]).trim().len() == 0 {
181 try!(fs::remove_file(fpath));
182 return Err(MainError::new("Contact emptied, file removed.").into());
183 };
184
185 Ok(())
186}
187
188fn mutt_query<'a>(config: &Configuration, query: &str) -> MainResult<()> {
189 println!(""); if let Ok(items) = utils::index_query(config, query) {
192 for item in items {
193 if item.email.len() > 0 && item.name.len() > 0 {
194 println!("{}\t{}", item.email, item.name);
195 };
196 };
197 };
198 Ok(())
199}
200
201fn file_query<'a>(config: &Configuration, query: &str) -> MainResult<()> {
202 for path in try!(utils::file_query(config, query)).iter() {
203 println!("{}", path.display());
204 };
205 Ok(())
206}
207
208fn email_query<'a>(config: &Configuration, query: &str) -> MainResult<()> {
209 for item in try!(utils::index_query(config, query)) {
210 if item.name.len() > 0 && item.email.len() > 0 {
211 println!("{} <{}>", item.name, item.email);
212 };
213 };
214 Ok(())
215}
216
217pub struct Configuration {
218 pub index_path: path::PathBuf,
219 pub vdir_path: path::PathBuf,
220 pub grep_cmd: String
221}
222
223impl Configuration {
224 pub fn new() -> Result<Configuration, String> {
225 Ok(Configuration {
226 index_path: match get_envvar("MATES_INDEX") {
227 Some(x) => path::PathBuf::from(&x),
228 None => match get_envvar("HOME") {
229 Some(home) => get_pwd().join(&home).join(".mates_index"),
230 None => return Err("Unable to determine user's home directory.".to_owned())
231 }
232 },
233 vdir_path: match get_envvar("MATES_DIR") {
234 Some(x) => path::PathBuf::from(&x),
235 None => return Err("MATES_DIR must be set to your vdir path (directory of vcf-files).".to_owned())
236 },
237 grep_cmd: match get_envvar("MATES_GREP") {
238 Some(x) => x,
239 None => "grep -i".to_owned()
240 }
241 })
242 }
243}
244
245
246#[derive(PartialEq, Eq, Debug)]
247pub struct MainError {
248 desc: String,
249}
250
251pub type MainResult<T> = Result<T, Box<Error>>;
252
253impl Error for MainError {
254 fn description(&self) -> &str {
255 &self.desc[..]
256 }
257
258 fn cause(&self) -> Option<&Error> {
259 None
260 }
261}
262
263impl fmt::Display for MainError {
264 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
265 self.description().fmt(f)
266 }
267}
268
269impl MainError {
270 pub fn new<T: Into<String>>(desc: T) -> Self {
271 MainError {
272 desc: desc.into(),
273 }
274 }
275}