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