libimagbookmarkfrontend/
lib.rs1#![forbid(unsafe_code)]
21
22#![deny(
23 non_camel_case_types,
24 non_snake_case,
25 path_statements,
26 trivial_numeric_casts,
27 unstable_features,
28 unused_allocation,
29 unused_import_braces,
30 unused_imports,
31 unused_must_use,
32 unused_mut,
33 unused_qualifications,
34 while_true,
35)]
36
37extern crate clap;
38#[macro_use] extern crate log;
39extern crate toml;
40extern crate url;
41extern crate uuid;
42extern crate toml_query;
43#[macro_use] extern crate failure;
44extern crate resiter;
45extern crate handlebars;
46extern crate rayon;
47
48extern crate libimagbookmark;
49extern crate libimagrt;
50extern crate libimagerror;
51extern crate libimagstore;
52extern crate libimagutil;
53extern crate libimagentryurl;
54
55use std::io::Write;
56use std::collections::BTreeMap;
57use std::process::Command;
58
59use failure::Error;
60use failure::err_msg;
61use failure::Fallible as Result;
62use resiter::AndThen;
63use resiter::IterInnerOkOrElse;
64use clap::App;
65use url::Url;
66use handlebars::Handlebars;
67use rayon::iter::ParallelIterator;
68use rayon::iter::IntoParallelIterator;
69use toml_query::read::TomlValueReadExt;
70
71use libimagrt::runtime::Runtime;
72use libimagrt::application::ImagApplication;
73use libimagstore::iter::get::StoreIdGetIteratorExtension;
74use libimagstore::store::FileLockEntry;
75use libimagbookmark::store::BookmarkStore;
76use libimagbookmark::bookmark::Bookmark;
77use libimagentryurl::link::Link;
78
79
80mod ui;
81
82pub enum ImagBookmark {}
87impl ImagApplication for ImagBookmark {
88 fn run(rt: Runtime) -> Result<()> {
89 match rt.cli().subcommand_name().ok_or_else(|| err_msg("No subcommand called"))? {
90 "add" => add(&rt),
91 "open" => open(&rt),
92 "list" => list(&rt),
93 "remove" => remove(&rt),
94 "find" => find(&rt),
95 other => {
96 debug!("Unknown command");
97 if rt.handle_unknown_subcommand("imag-bookmark", other, rt.cli())?.success() {
98 Ok(())
99 } else {
100 Err(err_msg("Failed to handle unknown subcommand"))
101 }
102 },
103 }
104 }
105
106 fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
107 ui::build_ui(app)
108 }
109
110 fn name() -> &'static str {
111 env!("CARGO_PKG_NAME")
112 }
113
114 fn description() -> &'static str {
115 "Bookmark collection tool"
116 }
117
118 fn version() -> &'static str {
119 env!("CARGO_PKG_VERSION")
120 }
121}
122
123fn add(rt: &Runtime) -> Result<()> {
124 let scmd = rt.cli().subcommand_matches("add").unwrap();
125 scmd.values_of("urls")
126 .unwrap()
127 .map(|s| Url::parse(s).map_err(Error::from))
128 .and_then_ok(|url| {
129 let (uuid, fle) = rt.store().add_bookmark(url.clone())?;
130 debug!("Created entry for url '{}' with uuid '{}'", url, uuid);
131 info!("{} = {}", url, uuid);
132 rt.report_touched(fle.get_location()).map_err(Error::from)
133 })
134 .collect()
135}
136
137fn open(rt: &Runtime) -> Result<()> {
138 let scmd = rt.cli().subcommand_matches("open").unwrap();
139 let open_command = rt.config()
140 .map(|value| {
141 value.read("bookmark.open")?
142 .ok_or_else(|| err_msg("Configuration missing: 'bookmark.open'"))?
143 .as_str()
144 .ok_or_else(|| err_msg("Open command should be a string"))
145 })
146 .or_else(|| Ok(scmd.value_of("opencmd")).transpose())
147 .unwrap_or_else(|| Err(err_msg("No open command available in config or on commandline")))?;
148
149 let hb = {
150 let mut hb = Handlebars::new();
151 hb.register_template_string("format", open_command)?;
152 hb
153 };
154
155 let iter = rt.ids::<crate::ui::PathProvider>()?
156 .ok_or_else(|| err_msg("No ids supplied"))?
157 .into_iter()
158 .map(Ok)
159 .into_get_iter(rt.store())
160 .map_inner_ok_or_else(|| err_msg("Did not find one entry"));
161
162 if scmd.is_present("openparallel") {
163 let links = iter
164 .and_then_ok(|link| rt.report_touched(link.get_location()).map_err(Error::from).map(|_| link))
165 .and_then_ok(|link| calculate_command_data(&hb, &link, open_command))
166 .collect::<Result<Vec<_>>>()?;
167
168 links
169 .into_par_iter()
170 .map(|command_rendered| {
171 Command::new(&command_rendered[0]) .args(&command_rendered[1..])
173 .output()
174 .map_err(Error::from)
175 })
176 .collect::<Result<Vec<_>>>()
177 .map(|_| ())
178 } else {
179 iter.and_then_ok(|link| {
180 rt.report_touched(link.get_location()).map_err(Error::from).map(|_| link)
181 })
182 .and_then_ok(|link| {
183 let command_rendered = calculate_command_data(&hb, &link, open_command)?;
184 Command::new(&command_rendered[0]) .args(&command_rendered[1..])
186 .output()
187 .map_err(Error::from)
188 })
189 .collect::<Result<Vec<_>>>()
190 .map(|_| ())
191 }
192}
193
194fn list(rt: &Runtime) -> Result<()> {
195 rt.store()
196 .all_bookmarks()?
197 .into_get_iter()
198 .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
199 .and_then_ok(|entry| {
200 if entry.is_bookmark()? {
201 let url = entry.get_url()?
202 .ok_or_else(|| format_err!("Failed to retrieve URL for {}", entry.get_location()))?;
203 if !rt.output_is_pipe() {
204 writeln!(rt.stdout(), "{}", url)?;
205 }
206
207 rt.report_touched(entry.get_location()).map_err(Error::from)
208 } else {
209 Ok(())
210 }
211 })
212 .collect()
213}
214
215fn remove(rt: &Runtime) -> Result<()> {
216 rt.ids::<crate::ui::PathProvider>()?
217 .ok_or_else(|| err_msg("No ids supplied"))?
218 .into_iter()
219 .map(Ok)
220 .into_get_iter(rt.store())
221 .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
222 .and_then_ok(|fle| {
223 rt.report_touched(fle.get_location())
224 .map_err(Error::from)
225 .and_then(|_| rt.store().remove_bookmark(fle))
226 })
227 .collect()
228}
229
230fn find(rt: &Runtime) -> Result<()> {
231 let substr = rt.cli().subcommand_matches("find").unwrap().value_of("substr").unwrap();
232
233 if let Some(ids) = rt.ids::<crate::ui::PathProvider>()? {
234 ids.into_iter()
235 .map(Ok)
236 .into_get_iter(rt.store())
237 } else {
238 rt.store()
239 .all_bookmarks()?
240 .into_get_iter()
241 }
242 .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
243 .and_then_ok(|fle| {
244 if fle.is_bookmark()? {
245 let url = fle
246 .get_url()?
247 .ok_or_else(|| format_err!("Failed to retrieve URL for {}", fle.get_location()))?;
248 if url.as_str().contains(substr) {
249 if !rt.output_is_pipe() {
250 writeln!(rt.stdout(), "{}", url)?;
251 }
252 rt.report_touched(fle.get_location()).map_err(Error::from)
253 } else {
254 Ok(())
255 }
256 } else {
257 Ok(())
258 }
259 })
260 .collect()
261}
262
263fn calculate_command_data<'a>(hb: &Handlebars, entry: &FileLockEntry<'a>, open_command: &str) -> Result<Vec<String>> {
264 let url = entry.get_url()?
265 .ok_or_else(|| format_err!("Failed to retrieve URL for {}", entry.get_location()))?
266 .into_string();
267
268 let data = {
269 let mut data = BTreeMap::new();
270 data.insert("url", url);
271 data
272 };
273
274 let command_rendered = hb.render("format", &data)?
275 .split_whitespace()
276 .map(String::from)
277 .collect::<Vec<String>>();
278
279 if command_rendered.len() > 2 {
280 return Err(format_err!("Command seems not to include URL: '{}'", open_command));
281 }
282
283 Ok(command_rendered.into_iter().map(String::from).collect())
284}