nuts_tool/
cli.rs

1// MIT License
2//
3// Copyright (c) 2023,2024 Robin Doer
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22
23pub mod archive;
24pub mod container;
25pub mod error;
26pub mod global;
27pub mod password;
28pub mod plugin;
29
30use anyhow::{anyhow, Result};
31use clap::{crate_version, Parser, Subcommand};
32use env_logger::Builder;
33use log::LevelFilter;
34use nuts_container::{Container, OpenOptionsBuilder};
35use nuts_tool_api::tool::Plugin;
36use rprompt::prompt_reply;
37
38use crate::backend::{PluginBackend, PluginBackendOpenBuilder};
39use crate::cli::archive::ArchiveArgs;
40use crate::cli::container::ContainerArgs;
41use crate::cli::global::{GlobalArgs, GLOBALS};
42use crate::cli::password::password_from_source;
43use crate::cli::plugin::PluginArgs;
44use crate::config::{ContainerConfig, PluginConfig};
45
46#[derive(Debug, Parser)]
47#[clap(name = "nuts", bin_name = "nuts")]
48#[clap(version = crate_version!())]
49pub struct NutsCli {
50    #[clap(subcommand)]
51    command: Commands,
52
53    #[command(flatten)]
54    global_args: GlobalArgs,
55}
56
57impl NutsCli {
58    pub fn configure_logging(&self) {
59        let filter = match self.global_args.verbose {
60            0 => LevelFilter::Off,
61            1 => LevelFilter::Info,
62            2 => LevelFilter::Debug,
63            _ => LevelFilter::Trace,
64        };
65
66        Builder::new().filter_level(filter).init();
67    }
68
69    pub fn run(&self) -> Result<()> {
70        self.global_args.init();
71
72        self.command.run()
73    }
74}
75
76#[derive(Debug, Subcommand)]
77pub enum Commands {
78    /// Configure plugins
79    Plugin(PluginArgs),
80
81    /// General container tasks
82    Container(ContainerArgs),
83
84    /// An archive on top of the container
85    Archive(ArchiveArgs),
86}
87
88impl Commands {
89    pub fn run(&self) -> Result<()> {
90        match self {
91            Self::Plugin(args) => args.run(),
92            Self::Container(args) => args.run(),
93            Self::Archive(args) => args.run(),
94        }
95    }
96}
97
98fn open_container(name: &str) -> Result<Container<PluginBackend>> {
99    let container_config = ContainerConfig::load()?;
100    let plugin_config = PluginConfig::load()?;
101    let verbose = GLOBALS.with_borrow(|g| g.verbose);
102
103    let plugin = container_config
104        .get_plugin(name)
105        .ok_or_else(|| anyhow!("no such container: {}", name))?;
106    let exe = plugin_config.path(plugin)?;
107
108    let plugin = Plugin::new(&exe);
109    let plugin_builder = PluginBackendOpenBuilder::new(plugin, name, verbose)?;
110
111    let builder = OpenOptionsBuilder::new().with_password_callback(password_from_source);
112    let options = builder.build::<PluginBackend>()?;
113
114    Container::open(plugin_builder, options).map_err(|err| err.into())
115}
116
117pub fn prompt_yes_no(prompt: &str, force: bool) -> Result<bool> {
118    let ok = force || {
119        let msg = format!("{} [yes/NO] ", prompt);
120        let reply = prompt_reply(msg)?;
121
122        reply == "yes"
123    };
124
125    Ok(ok)
126}