libimaggitcmd/
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] extern crate log;
39extern crate toml;
40extern crate toml_query;
41#[macro_use] extern crate failure;
42
43extern crate libimagrt;
44extern crate libimagerror;
45
46use std::io::ErrorKind;
47use std::process::Command;
48
49use toml::Value;
50use toml_query::read::TomlValueReadExt;
51use clap::App;
52use failure::Fallible as Result;
53use failure::ResultExt;
54use failure::err_msg;
55
56use libimagrt::runtime::Runtime;
57use libimagrt::application::ImagApplication;
58
59mod ui;
60
61/// Marker enum for implementing ImagApplication on
62///
63/// This is used by binaries crates to execute business logic
64/// or to build a CLI completion.
65pub enum ImagGit {}
66impl ImagApplication for ImagGit {
67    fn run(rt: Runtime) -> Result<()> {
68        let execute_in_store = rt
69            .config()
70            .ok_or_else(|| err_msg("No configuration. Please use git yourself, not via imag-git"))
71            .context("Won't continue without configuration.")
72            ?
73            .read("git.execute_in_store")
74            .context("Failed to read config setting 'git.execute_in_store'")
75            ?
76            .ok_or_else(|| err_msg("Missing config setting 'git.execute_in_store'"))
77            ?;
78
79        let execute_in_store = match *execute_in_store {
80            Value::Boolean(b) => Ok(b),
81            _ => Err(err_msg("Type error: 'git.execute_in_store' is not a boolean!")),
82        }?;
83
84        let execpath = if execute_in_store {
85            rt.store().path().to_str()
86        } else {
87            rt.rtp().to_str()
88        }
89        .map(String::from)
90        .ok_or_else(|| format_err!("Cannot parse to string: {:?}", rt.store().path()))?;
91
92        let mut command = Command::new("git");
93        command
94            .stdin(::std::process::Stdio::inherit())
95            .stdout(::std::process::Stdio::inherit())
96            .stderr(::std::process::Stdio::inherit())
97            .arg("-C").arg(&execpath);
98
99        let args = rt
100            .cli()
101            .values_of("")
102            .map(|vs| vs.map(String::from).collect())
103            .unwrap_or_else(|| vec![]);
104
105        debug!("Adding args = {:?}", args);
106        command.args(&args);
107
108        if let (external, Some(ext_m)) = rt.cli().subcommand() {
109            command.arg(external);
110            let args = ext_m
111                .values_of("")
112                .map(|vs| vs.map(String::from).collect())
113                .unwrap_or_else(|| vec![]);
114
115            debug!("Adding subcommand '{}' and args = {:?}", external, args);
116            command.args(&args);
117        }
118
119        debug!("Calling: {:?}", command);
120
121        match command.spawn().and_then(|mut c| c.wait()) {
122            Ok(exit_status) => {
123                if !exit_status.success() {
124                    debug!("git exited with non-zero exit code: {:?}", exit_status);
125                    Err(format_err!("git exited with non-zero exit code: {:?}", exit_status))
126                } else {
127                    debug!("Successful exit!");
128                    Ok(())
129                }
130            },
131
132            Err(e) => {
133                debug!("Error calling git");
134                Err(match e.kind() {
135                    ErrorKind::NotFound         => err_msg("Cannot find 'git' executable"),
136                    ErrorKind::PermissionDenied => err_msg("No permission to execute: 'git'"),
137                    _                           => format_err!("Error spawning: {:?}", e),
138                })
139            }
140        }
141    }
142
143    fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
144        ui::build_ui(app)
145    }
146
147    fn name() -> &'static str {
148        env!("CARGO_PKG_NAME")
149    }
150
151    fn description() -> &'static str {
152        "Helper to call git in the store"
153    }
154
155    fn version() -> &'static str {
156        env!("CARGO_PKG_VERSION")
157    }
158}