libimagcategorycmd/
lib.rs

1//
2// imag - the personal information management suite for the commandline
3// Copyright (C) 2015-2020 Matthias Beyer <mail@beyermatthias.de> and contributors
4//
5// This library is free software; you can redistribute it and/or
6// modify it under the terms of the GNU Lesser General Public
7// License as published by the Free Software Foundation; version
8// 2.1 of the License.
9//
10// This library is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13// Lesser General Public License for more details.
14//
15// You should have received a copy of the GNU Lesser General Public
16// License along with this library; if not, write to the Free Software
17// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18//
19
20#![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]
39extern crate log;
40#[macro_use]
41extern crate failure;
42extern crate resiter;
43
44extern crate libimagentrycategory;
45extern crate libimagerror;
46extern crate libimagrt;
47extern crate libimagstore;
48extern crate libimaginteraction;
49
50use failure::Fallible as Result;
51use resiter::Map;
52use clap::App;
53
54use libimagrt::runtime::Runtime;
55use libimagrt::iter::ReportTouchedResultEntry;
56use libimagrt::application::ImagApplication;
57
58mod ui;
59
60use std::io::Write;
61
62use failure::err_msg;
63use failure::Error;
64use resiter::AndThen;
65use resiter::IterInnerOkOrElse;
66
67use libimagentrycategory::store::CategoryStore;
68use libimagstore::iter::get::StoreIdGetIteratorExtension;
69use libimagentrycategory::entry::EntryCategory;
70use libimagentrycategory::category::Category;
71
72/// Marker enum for implementing ImagApplication on
73///
74/// This is used by binaries crates to execute business logic
75/// or to build a CLI completion.
76pub enum ImagCategory {}
77impl ImagApplication for ImagCategory {
78    fn run(rt: Runtime) -> Result<()> {
79        match rt.cli().subcommand_name().ok_or_else(|| err_msg("No subcommand called"))? {
80            "set"               => set(&rt),
81            "get"               => get(&rt),
82            "list-category"     => list_category(&rt),
83            "create-category"   => create_category(&rt),
84            "delete-category"   => delete_category(&rt),
85            "list-categories"   => list_categories(&rt),
86            other               => {
87                debug!("Unknown command");
88                if rt.handle_unknown_subcommand("imag-category", other, rt.cli())?.success() {
89                    Ok(())
90                } else {
91                    Err(err_msg("Failed to handle unknown subcommand"))
92                }
93            },
94        }
95    }
96
97    fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
98        ui::build_ui(app)
99    }
100
101    fn name() -> &'static str {
102        env!("CARGO_PKG_NAME")
103    }
104
105    fn description() -> &'static str {
106        "Add a category to entries and manage categories"
107    }
108
109    fn version() -> &'static str {
110        env!("CARGO_PKG_VERSION")
111    }
112}
113
114
115fn set(rt: &Runtime) -> Result<()> {
116    let scmd = rt.cli().subcommand_matches("set").unwrap(); // safed by main()
117    let name = scmd.value_of("set-name").map(String::from).unwrap(); // safed by clap
118    rt.ids::<crate::ui::PathProvider>()?
119        .ok_or_else(|| err_msg("No ids supplied"))?
120        .into_iter()
121        .map(Ok)
122        .into_get_iter(rt.store())
123        .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
124        .and_then_ok(|mut e| e.set_category_checked(rt.store(), &name).map(|_| e))
125        .map_report_touched(&rt)
126        .map_ok(|_| ())
127        .collect()
128}
129
130fn get(rt: &Runtime) -> Result<()> {
131    let out = rt.stdout();
132    let mut outlock = out.lock();
133    rt.ids::<crate::ui::PathProvider>()?
134        .ok_or_else(|| err_msg("No ids supplied"))?
135        .into_iter()
136        .map(Ok)
137        .into_get_iter(rt.store())
138        .map_inner_ok_or_else(|| err_msg("Did not find one entry"))
139        .map_report_touched(&rt)
140        .and_then_ok(|e| e.get_category())
141        .and_then_ok(|n| writeln!(outlock, "{}", n).map_err(Error::from))
142        .collect()
143}
144
145fn list_category(rt: &Runtime) -> Result<()> {
146    let scmd = rt.cli().subcommand_matches("list-category").unwrap(); // safed by main()
147    let name = scmd.value_of("list-category-name").map(String::from).unwrap(); // safed by clap
148
149    if let Some(category) = rt.store().get_category_by_name(&name)? {
150        let out         = rt.stdout();
151        let mut outlock = out.lock();
152
153        category
154            .get_entries(rt.store())?
155            .map_report_touched(&rt)
156            .map(|entry| writeln!(outlock, "{}", entry?.get_location()).map_err(Error::from))
157            .collect()
158    } else {
159        Err(format_err!("No category named '{}'", name))
160    }
161}
162
163fn create_category(rt: &Runtime) -> Result<()> {
164    let scmd = rt.cli().subcommand_matches("create-category").unwrap(); // safed by main()
165    let name = scmd.value_of("create-category-name").map(String::from).unwrap(); // safed by clap
166    rt.store()
167        .create_category(&name)
168        .and_then(|e| rt.report_touched(e.get_location()))
169}
170
171fn delete_category(rt: &Runtime) -> Result<()> {
172    use libimaginteraction::ask::ask_bool;
173
174    let scmd   = rt.cli().subcommand_matches("delete-category").unwrap(); // safed by main()
175    let name   = scmd.value_of("delete-category-name").map(String::from).unwrap(); // safed by clap
176    let ques   = format!("Do you really want to delete category '{}' and remove links to all categorized enties?", name);
177
178    let mut input  = rt.stdin().ok_or_else(|| err_msg("No input stream. Cannot ask for permission"))?;
179    let mut output = rt.stdout();
180    let answer = ask_bool(&ques, Some(false), &mut input, &mut output)?;
181
182    if answer {
183        info!("Deleting category '{}'", name);
184        rt.store().delete_category(&name)
185    } else {
186        info!("Not doing anything");
187        Ok(())
188    }
189}
190
191fn list_categories(rt: &Runtime) -> Result<()> {
192    let out         = rt.stdout();
193    let mut outlock = out.lock();
194
195    rt.store()
196        .all_category_names()?
197        .and_then_ok(|n| writeln!(outlock, "{}", n).map_err(Error::from))
198        .collect()
199}
200