cnf_lib/util/command_line.rs
1// SPDX-License-Identifier: GPL-3.0-or-later
2// SPDX-FileCopyrightText: (C) 2023 Andreas Hartmann <hartan@7x.de>
3// This file is part of cnf-lib, available at <https://gitlab.com/hartang/rust/cnf>
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 /// Tell whether privileges are needed to execute the command.
72 pub fn get_privileged(&self) -> bool {
73 self.privileged
74 }
75
76 /// Control whether command execution should be interactive.
77 pub fn is_interactive(&mut self, yesno: bool) {
78 self.interactive = yesno;
79 }
80
81 /// Tell if the command execution should be interactive.
82 pub fn get_interactive(&self) -> bool {
83 self.interactive
84 }
85
86 /// Append arguments to an existing commandline.
87 pub fn append<S: Clone + Into<String>>(&mut self, args: &[S]) {
88 args.iter()
89 .for_each(|arg| self.cmd.push(Into::<String>::into(arg.clone())))
90 }
91
92 /// Obtain the command (first "chunk") of the commandline.
93 pub fn command(&self) -> String {
94 self.cmd[0].clone()
95 }
96
97 /// Obtain the arguments of the commandline.
98 ///
99 /// Returns an empty slice if there are no arguments in the command line.
100 pub fn args(&self) -> &[String] {
101 self.cmd.get(1..).unwrap_or(&[])
102 }
103}
104
105impl fmt::Display for CommandLine {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 write!(f, "{} {}", self.command(), self.args().join(" "))
108 }
109}
110
111impl<S: Clone + Into<String>> From<Vec<S>> for CommandLine {
112 fn from(value: Vec<S>) -> Self {
113 Self::new(&value)
114 }
115}
116
117/// Conveniently create [`CommandLine`]s.
118#[macro_export]
119macro_rules! cmd {
120 ( $( $text:expr ),+ ) => {
121 CommandLine::new( &[ $( $text ),+ ] )
122 };
123}
124pub use cmd;