cnf_lib/util/
command_line.rs

1// Copyright (C) 2023 Andreas Hartmann <hartan@7x.de>
2// GNU General Public License v3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5//! Helpers for working with command lines.
6//!
7//! Provides [`CommandLine`] and the accompanying macro [`cmd!`], which abstract the trouble of
8//! assembling command lines for [`Environment`s](crate::env::Environment). Most importantly,
9//! [`CommandLine`] offers an [`Environment`](crate::env::Environment)-agnostic way of requesting
10//! privileges for command execution. This is useful when interacting with system package managers,
11//! for example, which often require root privileges for installation or updating caches.
12//!
13//! See [`CommandLine::privileged`] for more information.
14use serde_derive::{Deserialize, Serialize};
15use std::fmt;
16
17/// Convenient container for commandlines to execute in an [`crate::env::Environment`].
18#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq)]
19pub struct CommandLine {
20    /// The commandline to execute
21    cmd: Vec<String>,
22    /// True if execution requires privileges, false otherwise. If true and `interactive` is false,
23    /// execution may fail due to a lack of authentication.
24    #[serde(default)]
25    privileged: bool,
26    /// If set to true, privileges may be acquired interactively (e.g. using password prompts). If
27    /// false, privileges must be acquired non-internactively, or not at all.
28    #[serde(default)]
29    interactive: bool,
30}
31
32impl CommandLine {
33    /// Create a new instance of CommandLine.
34    ///
35    /// The given slice must contain at least one entry, otherwise a runtime error occurs.
36    pub fn new<S: Clone + Into<String>>(cmd: &[S]) -> Self {
37        debug_assert!(!cmd.is_empty(), "commandline mustn't be empty");
38
39        let as_vec = cmd
40            .iter()
41            .map(|s| Into::<String>::into(s.clone()))
42            .collect::<Vec<String>>();
43        Self {
44            cmd: as_vec,
45            privileged: false,
46            interactive: false,
47        }
48    }
49
50    /// True if nothing has been added to the commandline yet.
51    pub fn is_empty(&self) -> bool {
52        self.cmd.is_empty()
53    }
54
55    /// Request privileges for [`CommandLine`] on-the-fly.
56    ///
57    /// See [`CommandLine::needs_privileges`].
58    pub fn privileged(mut self) -> Self {
59        self.needs_privileges(true);
60        self
61    }
62
63    /// Control whether privileges are needed to execute the command.
64    ///
65    /// Privilege escalation is handled by each respective [`crate::env::Environment`] and on Unix
66    /// systems usually refers to some way of executing commands as the `root` user.
67    pub fn needs_privileges(&mut self, yesno: bool) {
68        self.privileged = yesno;
69    }
70
71    pub fn get_privileged(&self) -> bool {
72        self.privileged
73    }
74
75    /// Control whether command execution should be interactive.
76    pub fn is_interactive(&mut self, yesno: bool) {
77        self.interactive = yesno;
78    }
79
80    pub fn get_interactive(&self) -> bool {
81        self.interactive
82    }
83
84    /// Append arguments to an existing commandline.
85    pub fn append<S: Clone + Into<String>>(&mut self, args: &[S]) {
86        args.iter()
87            .for_each(|arg| self.cmd.push(Into::<String>::into(arg.clone())))
88    }
89
90    /// Obtain the command (first "chunk") of the commandline.
91    pub fn command(&self) -> String {
92        self.cmd[0].clone()
93    }
94
95    /// Obtain the arguments of the commandline.
96    ///
97    /// Returns an empty slice if there are no arguments in the command line.
98    pub fn args(&self) -> &[String] {
99        self.cmd.get(1..).unwrap_or(&[])
100    }
101}
102
103impl fmt::Display for CommandLine {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        write!(f, "{} {}", self.command(), self.args().join(" "))
106    }
107}
108
109impl<S: Clone + Into<String>> From<Vec<S>> for CommandLine {
110    fn from(value: Vec<S>) -> Self {
111        Self::new(&value)
112    }
113}
114
115/// Conveniently create [`CommandLine`]s.
116#[macro_export]
117macro_rules! cmd {
118    ( $( $text:expr ),+ ) => {
119        CommandLine::new( &[ $( $text ),+ ] )
120    };
121}
122pub use cmd;