libimagwikifrontend/
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
22extern crate clap;
23extern crate regex;
24extern crate filters;
25#[macro_use] extern crate log;
26#[macro_use] extern crate failure;
27extern crate resiter;
28
29extern crate libimagrt;
30extern crate libimagerror;
31extern crate libimagstore;
32extern crate libimagwiki;
33extern crate libimagentryedit;
34extern crate libimagentrylink;
35extern crate libimagutil;
36
37use std::io::Write;
38use failure::Fallible as Result;
39use failure::ResultExt;
40use failure::Error;
41use failure::err_msg;
42use clap::App;
43use resiter::AndThen;
44
45use libimagrt::runtime::Runtime;
46use libimagrt::application::ImagApplication;
47use libimagentryedit::edit::{Edit, EditHeader};
48use libimagwiki::store::WikiStore;
49use libimagwiki::entry::WikiEntry;
50
51mod ui;
52
53/// Marker enum for implementing ImagApplication on
54///
55/// This is used by binaries crates to execute business logic
56/// or to build a CLI completion.
57pub enum ImagWiki {}
58impl ImagApplication for ImagWiki {
59    fn run(rt: Runtime) -> Result<()> {
60        let wiki_name = rt.cli().value_of("wikiname").unwrap_or("default");
61        trace!("wiki_name = {}", wiki_name);
62        trace!("calling = {:?}", rt.cli().subcommand_name());
63
64        match rt.cli().subcommand_name().ok_or_else(|| err_msg("No subcommand called"))? {
65            "list"        => list(&rt, wiki_name),
66            "idof"        => idof(&rt, wiki_name),
67            "create"      => create(&rt, wiki_name),
68            "create-wiki" => create_wiki(&rt),
69            "show"        => show(&rt, wiki_name),
70            "delete"      => delete(&rt, wiki_name),
71            other         => {
72                debug!("Unknown command");
73                if rt.handle_unknown_subcommand("imag-wiki", other, rt.cli())?.success() {
74                    Ok(())
75                } else {
76                    Err(err_msg("Failed to handle unknown subcommand"))
77                }
78            }
79        } // end match scmd
80    }
81
82    fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
83        ui::build_ui(app)
84    }
85
86    fn name() -> &'static str {
87        env!("CARGO_PKG_NAME")
88    }
89
90    fn description() -> &'static str {
91        "Personal wiki"
92    }
93
94    fn version() -> &'static str {
95        env!("CARGO_PKG_VERSION")
96    }
97}
98
99
100fn list(rt: &Runtime, wiki_name: &str) -> Result<()> {
101    let scmd   = rt.cli().subcommand_matches("list").unwrap(); // safed by clap
102    let prefix = if scmd.is_present("list-full") {
103        format!("{}/", rt.store().path().display())
104    } else {
105        String::from("")
106    };
107
108    let out         = rt.stdout();
109    let mut outlock = out.lock();
110
111    rt.store()
112        .get_wiki(wiki_name)?
113        .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))?
114        .all_ids()?
115        .and_then_ok(|id| writeln!(outlock, "{}{}", prefix, id).map_err(Error::from))
116        .collect::<Result<Vec<_>>>()
117        .map(|_| ())
118}
119
120fn idof(rt: &Runtime, wiki_name: &str) -> Result<()> {
121    let scmd = rt.cli().subcommand_matches("idof").unwrap(); // safed by clap
122
123    let entryname = scmd
124        .value_of("idof-name")
125        .map(String::from)
126        .unwrap(); // safed by clap
127
128    let out      = rt.stdout();
129    let mut lock = out.lock();
130
131    rt.store()
132        .get_wiki(wiki_name)?
133        .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))?
134        .get_entry(&entryname)?
135        .ok_or_else(|| format_err!("Entry '{}' in wiki '{}' not found!", entryname, wiki_name))
136        .and_then(|entry| {
137            let id     = entry.get_location().clone();
138            let prefix = if scmd.is_present("idof-full") {
139                format!("{}/", rt.store().path().display())
140            } else {
141                String::from("")
142            };
143
144            writeln!(lock, "{}{}", prefix, id).map_err(Error::from)
145        })
146}
147
148fn create(rt: &Runtime, wiki_name: &str) -> Result<()> {
149    let scmd        = rt.cli().subcommand_matches("create").unwrap(); // safed by clap
150    let name        = String::from(scmd.value_of("create-name").unwrap()); // safe by clap
151
152    let wiki = rt
153        .store()
154        .get_wiki(&wiki_name)?
155        .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))?;
156
157    let mut entry = wiki.create_entry(name)?;
158
159    if !scmd.is_present("create-noedit") {
160        if scmd.is_present("create-editheader") {
161            entry.edit_header_and_content(rt)?;
162        } else {
163            entry.edit_content(rt)?;
164        }
165    }
166
167    if let Err(e) = entry
168        .autolink(rt.store())
169        .context("Linking has failed. Trying to safe the entry now. Please investigate by hand if this succeeds.")
170    {
171        rt.store().update(&mut entry).context("Safed entry")?;
172        return Err(e).map_err(Error::from)
173    }
174
175    let id = entry.get_location();
176
177    if scmd.is_present("create-printid") {
178        let out      = rt.stdout();
179        let mut lock = out.lock();
180
181        writeln!(lock, "{}", id)?;
182    }
183
184    rt.report_touched(&id).map_err(Error::from)
185}
186
187fn create_wiki(rt: &Runtime) -> Result<()> {
188    let scmd       = rt.cli().subcommand_matches("create-wiki").unwrap(); // safed by clap
189    let wiki_name  = String::from(scmd.value_of("create-wiki-name").unwrap()); // safe by clap
190    let (_, index) = rt.store().create_wiki(&wiki_name)?;
191
192    rt.report_touched(index.get_location()).map_err(Error::from)
193}
194
195fn show(rt: &Runtime, wiki_name: &str) -> Result<()> {
196    use filters::filter::Filter;
197
198    let scmd  = rt.cli().subcommand_matches("show").unwrap(); // safed by clap
199
200    struct NameFilter(Option<Vec<String>>);
201    impl Filter<String> for NameFilter {
202        fn filter(&self, e: &String) -> bool {
203            match self.0 {
204                Some(ref v) => v.contains(e),
205                None        => false,
206            }
207        }
208    }
209
210    let namefilter = NameFilter(scmd
211                                .values_of("show-name")
212                                .map(|v| v.map(String::from).collect::<Vec<String>>()));
213
214    let wiki = rt
215        .store()
216        .get_wiki(&wiki_name)?
217        .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))?;
218
219    let out         = rt.stdout();
220    let mut outlock = out.lock();
221
222    scmd.values_of("show-name")
223        .unwrap() // safe by clap
224        .map(String::from)
225        .filter(|e| namefilter.filter(e))
226        .map(|name| {
227            let entry = wiki
228                .get_entry(&name)?
229                .ok_or_else(|| format_err!("No wiki entry '{}' found in wiki '{}'", name, wiki_name))?;
230
231            writeln!(outlock, "{}", entry.get_location())?;
232            writeln!(outlock, "{}", entry.get_content())?;
233
234            rt.report_touched(entry.get_location()).map_err(Error::from)
235        })
236        .collect::<Result<Vec<_>>>()
237        .map(|_| ())
238}
239
240fn delete(rt: &Runtime, wiki_name: &str) -> Result<()> {
241    use libimagentrylink::linkable::Linkable;
242
243    let scmd   = rt.cli().subcommand_matches("delete").unwrap(); // safed by clap
244    let name   = String::from(scmd.value_of("delete-name").unwrap()); // safe by clap
245    let unlink = !scmd.is_present("delete-no-remove-linkings");
246
247    let wiki = rt
248            .store()
249            .get_wiki(&wiki_name)?
250            .ok_or_else(|| format_err!("No wiki '{}' found", wiki_name))?;
251
252    if unlink {
253        wiki.get_entry(&name)?
254            .ok_or_else(|| format_err!("No wiki entry '{}' in '{}' found", name, wiki_name))?
255            .unlink(rt.store())?;
256    }
257
258    wiki.delete_entry(&name)
259}
260