up_rs/
exec.rs

1//! Wrappers around executing commands.
2
3use crate::log;
4use camino::Utf8Path;
5use duct::Expression;
6use std::ffi::OsString;
7use std::fmt::Write;
8use std::io;
9use std::process::Output;
10use tracing::Level;
11
12/// Copy of the `duct::cmd` function that ensures we're info logging the command we're running.
13pub fn cmd<T, U>(program: T, args: U) -> Expression
14where
15    T: duct::IntoExecutablePath + Clone,
16    U: IntoIterator + Clone,
17    <U as IntoIterator>::Item: Into<OsString>,
18{
19    cmd_log(Level::INFO, program, args)
20}
21
22/// Wrapper around `duct::cmd` function that lets us log the command we're running.
23pub fn cmd_log<T, U>(l: Level, program: T, args: U) -> Expression
24where
25    T: duct::IntoExecutablePath + Clone,
26    U: IntoIterator + Clone,
27    U::Item: Into<OsString>,
28{
29    let mut formatted_cmd = format!(
30        "Running command: {program}",
31        program = shell_escape::escape(program.clone().to_executable().to_string_lossy())
32    );
33    for arg in args.clone() {
34        write!(
35            formatted_cmd,
36            " {arg}",
37            arg = shell_escape::escape(arg.into().to_string_lossy())
38        )
39        .unwrap();
40    }
41
42    log!(l, "{formatted_cmd}");
43
44    duct::cmd(program, args)
45}
46
47/// Copy of the `duct::cmd!` macro that ensures we're logging the command we're running at the
48/// 'info' level (logged by default).
49#[macro_export]
50macro_rules! cmd {
51    ( $program:expr $(, $arg:expr )* $(,)? ) => {
52        {
53            use std::ffi::OsString;
54            let args: &[OsString] = &[$( Into::<OsString>::into($arg) ),*];
55            $crate::exec::cmd_log(tracing::Level::INFO, $program, args)
56        }
57    };
58}
59
60/// Copy of the `duct::cmd!` macro that ensures we're logging the command we're running at the debug
61/// level (not logged by default).
62#[macro_export]
63macro_rules! cmd_debug {
64    ( $program:expr $(, $arg:expr )* $(,)? ) => {
65        {
66            use std::ffi::OsString;
67            let args: &[OsString] = &[$( Into::<OsString>::into($arg) ),*];
68            $crate::exec::cmd_log(tracing::Level::DEBUG, $program, args)
69        }
70    };
71}
72
73/// Copy of the `duct::cmd!` macro that skips running the command if the `dry_run` boolean is
74/// `true`. Logs the command to be run at the `Info` level.
75#[macro_export]
76macro_rules! cmd_if_wet {
77    ( $dry_run:expr, $program:expr $(, $arg:expr )* $(,)? ) => {
78        {
79            use std::ffi::OsString;
80            let mut actual_program = $program;
81            let args: Vec<OsString> = if $dry_run {
82                actual_program = "true";
83                vec![OsString::from("[Dry Run]"), Into::<OsString>::into($program), $( Into::<OsString>::into($arg) ),*]
84            } else {
85                vec![$( Into::<OsString>::into($arg) ),*]
86            };
87            $crate::exec::cmd_log(tracing::Level::INFO, actual_program, &args)
88        }
89    };
90}
91
92/// Copy of the `duct::cmd!` macro that skips running the command if the `dry_run` boolean is
93/// `true`. Logs the command to be run at the `Debug` level.
94#[macro_export]
95macro_rules! cmd_debug_if_wet {
96    ( $dry_run:expr, $program:expr $(, $arg:expr )* $(,)? ) => {
97        {
98            use std::ffi::OsString;
99            let mut actual_program = $program;
100            let args: Vec<OsString> = if $dry_run {
101                actual_program = "true";
102                vec![OsString::from("[Dry Run]"), Into::<OsString>::into($program), $( Into::<OsString>::into($arg) ),*]
103            } else {
104                vec![$( Into::<OsString>::into($arg) ),*]
105            };
106            $crate::exec::cmd_log(tracing::Level::DEBUG, actual_program, &args)
107        }
108    };
109}
110
111/// Trait to wrap running commands with Duct.
112pub trait UpDuct {
113    /**
114    Run with the stdout sent to wherever `stdout_fn` points to.
115
116    You should normally use this instead of the `.run()` function, to make sure you don't
117    accidentally write stdout to liv's stdout, as this pollutes stdout, and may cause
118    liv commands to fail for users.
119    */
120    fn run_with(&self, stdout_fn: fn(&Expression) -> Expression) -> io::Result<Output>;
121
122    /**
123    Run with the stdout sent to path `path`.
124
125    Alternative to the `.run_with()` function as this takes a path argument.
126    */
127    fn run_with_path(&self, path: &Utf8Path) -> io::Result<Output>;
128
129    /// Run with the stdout inherited from the parent process.
130    fn run_with_inherit(&self) -> io::Result<Output>;
131}
132
133impl UpDuct for Expression {
134    /// Run with the stdout sent to wherever `stdout_fn` points to.
135    fn run_with(&self, stdout_fn: fn(&Expression) -> Expression) -> io::Result<Output> {
136        // This method is blocked elsewhere to force people to use the `.run_with*()` functions.
137        // So we need to be able to use it here.
138        #[allow(clippy::disallowed_methods)]
139        stdout_fn(self).run()
140    }
141
142    /// Run with the stdout sent to wherever `stdout_fn` points to.
143    fn run_with_path(&self, path: &Utf8Path) -> io::Result<Output> {
144        // This method is blocked elsewhere to force people to use the `.run_with*()` functions.
145        // So we need to be able to use it here.
146        #[allow(clippy::disallowed_methods)]
147        self.stdout_path(path).run()
148    }
149
150    /// Run with the stdout inherited from the parent process.
151    fn run_with_inherit(&self) -> io::Result<Output> {
152        // This method is blocked elsewhere to force people to use the `.run_with*()` functions.
153        // So we need to be able to use it here.
154        #[allow(clippy::disallowed_methods)]
155        self.run()
156    }
157}