1#![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
37#[macro_use] extern crate log;
38extern crate clap;
39extern crate url;
40#[macro_use] extern crate failure;
41#[macro_use] extern crate prettytable;
42#[cfg(test)] extern crate toml;
43#[cfg(test)] extern crate toml_query;
44#[cfg(test)] extern crate env_logger;
45
46extern crate libimagentrylink;
47extern crate libimagentryurl;
48extern crate libimagrt;
49extern crate libimagstore;
50extern crate libimagerror;
51
52#[cfg(test)]
53#[macro_use]
54extern crate libimagutil;
55
56#[cfg(not(test))]
57extern crate libimagutil;
58
59use std::io::Write;
60use std::path::PathBuf;
61
62
63use failure::err_msg;
64
65use libimagentryurl::linker::UrlLinker;
66use libimagentrylink::linkable::Linkable;
67use libimagentrylink::storecheck::StoreLinkConsistentExt;
68use libimagrt::runtime::Runtime;
69use libimagrt::application::ImagApplication;
70use libimagstore::store::FileLockEntry;
71use libimagstore::storeid::StoreId;
72
73use url::Url;
74use failure::Fallible as Result;
75use clap::App;
76
77mod ui;
78
79pub enum ImagLink {}
84impl ImagApplication for ImagLink {
85 fn run(rt: Runtime) -> Result<()> {
86 if rt.cli().is_present("check-consistency") {
87 rt.store().check_link_consistency()?;
88 info!("Store is consistent");
89 }
90
91 if let Some(name) = rt.cli().subcommand_name() {
92 match name {
93 "remove" => remove_linking(&rt),
94 "unlink" => unlink(&rt),
95 "list" => list_linkings(&rt),
96 other => {
97 debug!("Unknown command");
98 if rt.handle_unknown_subcommand("imag-link", other, rt.cli())?.success() {
99 Ok(())
100 } else {
101 Err(format_err!("Subcommand failed"))
102 }
103 },
104 }
105 } else if let (Some(from), Some(to)) = (rt.cli().value_of("from"), rt.cli().values_of("to")) {
106 link_from_to(&rt, from, to)
107 } else {
108 Err(err_msg("No commandline call"))
109 }
110 }
111
112 fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
113 ui::build_ui(app)
114 }
115
116 fn name() -> &'static str {
117 env!("CARGO_PKG_NAME")
118 }
119
120 fn description() -> &'static str {
121 "Link entries"
122 }
123
124 fn version() -> &'static str {
125 env!("CARGO_PKG_VERSION")
126 }
127}
128
129fn get_entry_by_name<'a>(rt: &'a Runtime, name: &str) -> Result<Option<FileLockEntry<'a>>> {
130 debug!("Getting: {:?}", name);
131 let result = StoreId::new(PathBuf::from(name)).and_then(|id| rt.store().get(id));
132
133 debug!(" => : {:?}", result);
134 result
135}
136
137fn link_from_to<'a, I>(rt: &'a Runtime, from: &'a str, to: I) -> Result<()>
138 where I: Iterator<Item = &'a str>
139{
140 let directional = rt.cli().is_present("directional");
141 let mut from_entry = get_entry_by_name(rt, from)?.ok_or_else(|| err_msg("No 'from' entry"))?;
142
143 for entry in to {
144 debug!("Handling 'to' entry: {:?}", entry);
145 if rt.store().get(PathBuf::from(entry))?.is_none() {
146 debug!("Linking externally: {:?} -> {:?}", from, entry);
147 let url = Url::parse(entry).map_err(|e| format_err!("Error parsing URL: {:?}", e))?;
148
149 let iter = from_entry
150 .add_url(rt.store(), url)?
151 .into_iter();
152
153 rt.report_all_touched(iter)?;
154 } else {
155 debug!("Linking internally: {:?} -> {:?}", from, entry);
156
157 let from_id = StoreId::new(PathBuf::from(from))?;
158 let entr_id = StoreId::new(PathBuf::from(entry))?;
159
160 if from_id == entr_id {
161 return Err(err_msg("Cannot link entry with itself. Exiting"))
162 }
163
164 let mut to_entry = rt
165 .store()
166 .get(entr_id)?
167 .ok_or_else(|| format_err!("No 'to' entry: {}", entry))?;
168
169 if directional {
170 from_entry.add_link_to(&mut to_entry)?;
171 } else {
172 from_entry.add_link(&mut to_entry)?;
173 }
174
175 rt.report_touched(to_entry.get_location())?;
176 }
177
178 info!("Ok: {} -> {}", from, entry);
179 }
180
181 rt.report_touched(from_entry.get_location())
182}
183
184fn remove_linking(rt: &Runtime) -> Result<()> {
185 let mut from : FileLockEntry = rt.cli()
186 .subcommand_matches("remove")
187 .unwrap() .value_of("from")
189 .map(PathBuf::from)
190 .and_then(|id| rt.store().get(id).transpose())
191 .ok_or_else(|| err_msg("No 'from' entry"))??;
192
193 rt
194 .ids::<crate::ui::PathProvider>()?
195 .ok_or_else(|| err_msg("No ids supplied"))?
196 .into_iter()
197 .map(|id| match rt.store().get(id.clone())? {
198 Some(mut to_entry) => {
199 to_entry.remove_link(&mut from)?;
200 rt.report_touched(to_entry.get_location())
201 },
202
203 None => {
204 if id.local().is_file() {
207 let pb = id.local().to_str().ok_or_else(|| format_err!("Not StoreId and not a Path: {}", id))?;
208 let url = Url::parse(pb).map_err(|e| format_err!("Error parsing URL: {:?}", e))?;
209 from.remove_url(rt.store(), url)?;
210 info!("Ok: {}", id);
211 Ok(())
212 } else {
213 Err(format_err!("Entry not found: {:?}", id))
214 }
215 }
216 })
217 .collect::<Result<Vec<_>>>()?;
218
219 rt.report_touched(from.get_location())
220}
221
222fn unlink(rt: &Runtime) -> Result<()> {
223 rt
224 .ids::<crate::ui::PathProvider>()?
225 .ok_or_else(|| err_msg("No ids supplied"))?
226 .into_iter()
227 .map(|id| {
228 rt.store()
229 .get(id.clone())?
230 .ok_or_else(|| format_err!("No entry for {}", id))?
231 .unlink(rt.store())?;
232
233 rt.report_touched(&id)
234 })
235 .collect()
236}
237
238fn list_linkings(rt: &Runtime) -> Result<()> {
239 let cmd = rt.cli()
240 .subcommand_matches("list")
241 .unwrap(); let list_externals = cmd.is_present("list-externals-too");
244 let list_plain = cmd.is_present("list-plain");
245
246 let mut tab = ::prettytable::Table::new();
247 tab.set_titles(row!["#", "Link"]);
248
249 rt.ids::<crate::ui::PathProvider>()?
250 .ok_or_else(|| err_msg("No ids supplied"))?
251 .into_iter()
252 .map(|id| {
253 let entry = rt.store().get(id.clone())?.ok_or_else(|| format_err!("Not found: {}", id))?;
254
255 for (i, link) in entry.links()?.enumerate() {
256 let link = link.to_str()?;
257
258 if list_plain {
259 writeln!(rt.stdout(), "{: <3}: {}", i, link)?;
260 } else {
261 tab.add_row(row![i, link]);
262 }
263 }
264
265 if list_externals {
266 entry.get_urls(rt.store())?
267 .enumerate()
268 .map(|(i, link)| {
269 let link = link?.into_string();
270
271 if list_plain {
272 writeln!(rt.stdout(), "{: <3}: {}", i, link)?;
273 } else {
274 tab.add_row(row![i, link]);
275 }
276
277 Ok(())
278 })
279 .collect::<Result<Vec<_>>>()?;
280 }
281
282 rt.report_touched(entry.get_location())
283 })
284 .collect::<Result<Vec<_>>>()?;
285
286 if !list_plain {
287 let out = rt.stdout();
288 let mut lock = out.lock();
289 tab.print(&mut lock)?;
290 }
291
292 Ok(())
293}
294
295#[cfg(test)]
296mod tests {
297 use super::link_from_to;
298 use super::remove_linking;
299
300 use std::path::PathBuf;
301 use std::ffi::OsStr;
302
303 use toml::value::Value;
304 use toml_query::read::TomlValueReadExt;
305 use failure::Fallible as Result;
306 use failure::Error;
307
308 use libimagrt::runtime::Runtime;
309 use libimagstore::storeid::StoreId;
310 use libimagstore::store::{FileLockEntry, Entry};
311
312 fn setup_logging() {
313 let _ = ::env_logger::try_init();
314 }
315
316 make_mock_app! {
317 app "imag-link";
318 modulename mock;
319 version env!("CARGO_PKG_VERSION");
320 with help "imag-link mocking app";
321 with ui builder function crate::ui::build_ui;
322 }
323 use self::mock::generate_test_runtime;
324 use self::mock::reset_test_runtime;
325
326 fn create_test_default_entry<'a, S: AsRef<OsStr>>(rt: &'a Runtime, name: S) -> Result<StoreId> {
327 let mut path = PathBuf::new();
328 path.set_file_name(name);
329
330 let default_entry = Entry::new(StoreId::new(PathBuf::from("")).unwrap())
331 .to_str()
332 .unwrap();
333
334 debug!("Default entry constructed");
335
336 let id = StoreId::new(path)?;
337 debug!("StoreId constructed: {:?}", id);
338
339 let mut entry = rt.store().create(id.clone())?;
340
341 debug!("Entry constructed: {:?}", id);
342 entry.get_content_mut().push_str(&default_entry);
343
344 Ok(id)
345 }
346
347 fn get_entry_links<'a>(entry: &'a FileLockEntry<'a>) -> Result<&'a Value> {
348 match entry.get_header().read(&"links.internal".to_owned()).map_err(Error::from)? {
349 Some(v) => Ok(v),
350 None => panic!("Didn't find 'links' in {:?}", entry),
351 }
352 }
353
354 fn links_toml_value<I: IntoIterator<Item = &'static str>>(links: I) -> Value {
355 Value::Array(links
356 .into_iter()
357 .map(|s| Value::String(s.to_owned()))
358 .collect())
359 }
360
361 #[test]
362 fn test_link_modificates() {
363 setup_logging();
364 let rt = generate_test_runtime(vec!["test1", "test2"])
365 .unwrap();
366
367 debug!("Runtime created");
368
369 let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
370 let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
371
372 debug!("Entries created");
373
374 link_from_to(&rt, "test1", vec!["test2"].into_iter()).unwrap();
375
376 debug!("Linking done");
377
378 let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
379 let test_links1 = get_entry_links(&test_entry1).unwrap();
380
381 let test_entry2 = rt.store().get(test_id2).unwrap().unwrap();
382 let test_links2 = get_entry_links(&test_entry2).unwrap();
383
384 debug!("Asserting");
385
386 assert_ne!(*test_links1, links_toml_value(vec![]));
387 assert_ne!(*test_links2, links_toml_value(vec![]));
388
389 debug!("Test finished")
390 }
391
392 #[test]
393 fn test_linking_links() {
394 setup_logging();
395 let rt = generate_test_runtime(vec!["test1", "test2"])
396 .unwrap();
397
398 debug!("Runtime created");
399
400 let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
401 let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
402
403 debug!("Test entries created");
404
405 link_from_to(&rt, "test1", vec!["test2"].into_iter()).unwrap();
406
407 debug!("Linking done");
408
409 let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
410 let test_links1 = get_entry_links(&test_entry1).unwrap();
411
412 let test_entry2 = rt.store().get(test_id2).unwrap().unwrap();
413 let test_links2 = get_entry_links(&test_entry2).unwrap();
414
415 debug!("Asserting");
416
417 assert_eq!(*test_links1, links_toml_value(vec!["test2"]));
418 assert_eq!(*test_links2, links_toml_value(vec!["test1"]));
419 }
420
421 #[test]
422 fn test_multilinking() {
423 setup_logging();
424 let rt = generate_test_runtime(vec!["test1", "test2"])
425 .unwrap();
426
427 debug!("Runtime created");
428
429 let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
430 let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
431
432 debug!("Test entries created");
433
434 link_from_to(&rt, "test1", vec!["test2"].into_iter()).unwrap();
435 link_from_to(&rt, "test1", vec!["test2"].into_iter()).unwrap();
436
437 debug!("Linking done");
438
439 let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
440 let test_links1 = get_entry_links(&test_entry1).unwrap();
441
442 let test_entry2 = rt.store().get(test_id2).unwrap().unwrap();
443 let test_links2 = get_entry_links(&test_entry2).unwrap();
444
445 debug!("Asserting");
446
447 assert_eq!(*test_links1, links_toml_value(vec!["test2"]));
448 assert_eq!(*test_links2, links_toml_value(vec!["test1"]));
449 }
450
451 #[test]
452 fn test_linking_more_than_two() {
453 setup_logging();
454 let rt = generate_test_runtime(vec!["test1", "test2", "test3"])
455 .unwrap();
456
457 debug!("Runtime created");
458
459 let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
460 let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
461 let test_id3 = create_test_default_entry(&rt, "test3").unwrap();
462
463 debug!("Test entries created");
464
465 link_from_to(&rt, "test1", vec!["test2", "test3"].into_iter()).unwrap();
466 link_from_to(&rt, "test1", vec!["test2", "test3"].into_iter()).unwrap();
467
468 debug!("Linking done");
469
470 let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
471 let test_links1 = get_entry_links(&test_entry1).unwrap();
472
473 let test_entry2 = rt.store().get(test_id2).unwrap().unwrap();
474 let test_links2 = get_entry_links(&test_entry2).unwrap();
475
476 let test_entry3 = rt.store().get(test_id3).unwrap().unwrap();
477 let test_links3 = get_entry_links(&test_entry3).unwrap();
478
479 debug!("Asserting");
480
481 assert_eq!(*test_links1, links_toml_value(vec!["test2", "test3"]));
482 assert_eq!(*test_links2, links_toml_value(vec!["test1"]));
483 assert_eq!(*test_links3, links_toml_value(vec!["test1"]));
484 }
485
486 #[test]
489 fn test_linking_links_unlinking_removes_links() {
490 setup_logging();
491 let rt = generate_test_runtime(vec!["test1", "test2"])
492 .unwrap();
493
494 debug!("Runtime created");
495
496 let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
497 let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
498
499 debug!("Test entries created");
500
501 link_from_to(&rt, "test1", vec!["test2"].into_iter()).unwrap();
502
503 debug!("Linking done");
504
505 let rt = reset_test_runtime(vec!["remove", "test1", "test2"], rt)
506 .unwrap();
507
508 remove_linking(&rt).unwrap();
509
510 debug!("Linking removed");
511
512 let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
513 let test_links1 = get_entry_links(&test_entry1).unwrap();
514
515 let test_entry2 = rt.store().get(test_id2).unwrap().unwrap();
516 let test_links2 = get_entry_links(&test_entry2).unwrap();
517
518 debug!("Asserting");
519
520 assert_eq!(*test_links1, links_toml_value(vec![]));
521 assert_eq!(*test_links2, links_toml_value(vec![]));
522 }
523
524 #[test]
525 fn test_linking_and_unlinking_more_than_two() {
526 setup_logging();
527 let rt = generate_test_runtime(vec!["test1", "test2", "test3"])
528 .unwrap();
529
530 debug!("Runtime created");
531
532 let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
533 let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
534 let test_id3 = create_test_default_entry(&rt, "test3").unwrap();
535
536 debug!("Test entries created");
537
538 link_from_to(&rt, "test1", vec!["test2", "test3"].into_iter()).unwrap();
539
540 debug!("linking done");
541
542 let rt = reset_test_runtime(vec!["remove", "test1", "test2", "test3"], rt)
543 .unwrap();
544
545 remove_linking(&rt).unwrap();
546
547 debug!("linking removed");
548
549 let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
550 let test_links1 = get_entry_links(&test_entry1).unwrap();
551
552 let test_entry2 = rt.store().get(test_id2).unwrap().unwrap();
553 let test_links2 = get_entry_links(&test_entry2).unwrap();
554
555 let test_entry3 = rt.store().get(test_id3).unwrap().unwrap();
556 let test_links3 = get_entry_links(&test_entry3).unwrap();
557
558 debug!("Asserting");
559
560 assert_eq!(*test_links1, links_toml_value(vec![]));
561 assert_eq!(*test_links2, links_toml_value(vec![]));
562 assert_eq!(*test_links3, links_toml_value(vec![]));
563 }
564}