bevy_local_commands/
local_command.rs

1use std::{
2    ffi::OsStr,
3    fmt::Debug,
4    path::Path,
5    process::{Command, CommandArgs, CommandEnvs},
6};
7
8use bevy::prelude::*;
9
10#[derive(Component)]
11pub struct LocalCommand {
12    pub(crate) command: Command,
13    pub(crate) delay: Option<Timer>,
14    pub(crate) state: LocalCommandState,
15}
16
17/// Keep track of the state of the running process.
18///
19/// This gives more room for addons to interact with the process without interfering with each other.
20#[derive(Debug, PartialEq)]
21pub enum LocalCommandState {
22    /// Ready - Process is ready to be run - pending start time.
23    Ready,
24    /// Running - Process is running.
25    Running,
26    /// Error - Process errored out. Allows for retry logic to kick in. Otherwise state moves to Done.
27    Error,
28    /// Done - Process has completed. Final state, allows for cleanup logic.
29    Done(LocalCommandDone),
30}
31
32/// Keeps track of the final state of the process.
33#[derive(Debug, PartialEq)]
34pub enum LocalCommandDone {
35    /// Killed - Process was killed. Allows for cleanup logic.
36    Killed,
37    /// Failed - Process failed permanently. Assumes retries exhausted. Allows for cleanup logic.
38    Failed,
39    /// Succeeded - Process succeeded. Allows for cleanup logic.
40    Succeeded,
41}
42
43impl LocalCommand {
44    pub fn new<S>(program: S) -> Self
45    where
46        S: AsRef<OsStr>,
47    {
48        Self {
49            command: Command::new(program),
50            delay: None,
51            state: LocalCommandState::Ready,
52        }
53    }
54
55    /// Adds an argument to pass to the program.
56    ///
57    /// Only one argument can be passed per use. So instead of:
58    ///
59    /// ```no_run
60    /// # bevy_local_commands::LocalCommand::new("sh")
61    /// .arg("-C /path/to/repo")
62    /// # ;
63    /// ```
64    ///
65    /// usage would be:
66    ///
67    /// ```no_run
68    /// # bevy_local_commands::LocalCommand::new("sh")
69    /// .arg("-C")
70    /// .arg("/path/to/repo")
71    /// # ;
72    /// ```
73    ///
74    /// To pass multiple arguments see [`args`].
75    ///
76    /// [`args`]: LocalCommand::args
77    ///
78    /// Note that the argument is not passed through a shell, but given
79    /// literally to the program. This means that shell syntax like quotes,
80    /// escaped characters, word splitting, glob patterns, variable substitution, etc.
81    /// have no effect.
82    pub fn arg<S: AsRef<OsStr>>(mut self, arg: S) -> Self {
83        self.command.arg(arg);
84        self
85    }
86
87    /// Adds multiple arguments to pass to the program.
88    ///
89    /// To pass a single argument see [`arg`].
90    ///
91    /// [`arg`]: LocalCommand::arg
92    ///
93    /// Note that the arguments are not passed through a shell, but given
94    /// literally to the program. This means that shell syntax like quotes,
95    /// escaped characters, word splitting, glob patterns, variable substitution, etc.
96    /// have no effect.
97    pub fn args<I, S>(mut self, args: I) -> Self
98    where
99        I: IntoIterator<Item = S>,
100        S: AsRef<OsStr>,
101    {
102        self.command.args(args);
103        self
104    }
105
106    /// Inserts or updates an explicit environment variable mapping.
107    ///
108    /// This method allows you to add an environment variable mapping to the spawned process or
109    /// overwrite a previously set value. You can use [`LocalCommand::envs`] to set multiple environment
110    /// variables simultaneously.
111    ///
112    /// Child processes will inherit environment variables from their parent process by default.
113    /// Environment variables explicitly set using [`LocalCommand::env`] take precedence over inherited
114    /// variables. You can disable environment variable inheritance entirely using
115    /// [`LocalCommand::env_clear`] or for a single key using [`LocalCommand::env_remove`].
116    ///
117    /// Note that environment variable names are case-insensitive (but
118    /// case-preserving) on Windows and case-sensitive on all other platforms.
119    pub fn env<K, V>(mut self, key: K, val: V) -> Self
120    where
121        K: AsRef<OsStr>,
122        V: AsRef<OsStr>,
123    {
124        self.command.env(key, val);
125        self
126    }
127
128    /// Inserts or updates multiple explicit environment variable mappings.
129    ///
130    /// This method allows you to add multiple environment variable mappings to the spawned process
131    /// or overwrite previously set values. You can use [`LocalCommand::env`] to set a single environment
132    /// variable.
133    ///
134    /// Child processes will inherit environment variables from their parent process by default.
135    /// Environment variables explicitly set using [`LocalCommand::envs`] take precedence over inherited
136    /// variables. You can disable environment variable inheritance entirely using
137    /// [`LocalCommand::env_clear`] or for a single key using [`LocalCommand::env_remove`].
138    ///
139    /// Note that environment variable names are case-insensitive (but case-preserving) on Windows
140    /// and case-sensitive on all other platforms.
141    pub fn envs<I, K, V>(mut self, vars: I) -> Self
142    where
143        I: IntoIterator<Item = (K, V)>,
144        K: AsRef<OsStr>,
145        V: AsRef<OsStr>,
146    {
147        self.command.envs(vars);
148        self
149    }
150
151    /// Removes an explicitly set environment variable and prevents inheriting it from a parent
152    /// process.
153    ///
154    /// This method will remove the explicit value of an environment variable set via
155    /// [`LocalCommand::env`] or [`LocalCommand::envs`]. In addition, it will prevent the spawned child
156    /// process from inheriting that environment variable from its parent process.
157    ///
158    /// After calling [`LocalCommand::env_remove`], the value associated with its key from
159    /// [`LocalCommand::get_envs`] will be [`None`].
160    ///
161    /// To clear all explicitly set environment variables and disable all environment variable
162    /// inheritance, you can use [`LocalCommand::env_clear`].
163    pub fn env_remove<K: AsRef<OsStr>>(mut self, key: K) -> Self {
164        self.command.env_remove(key);
165        self
166    }
167
168    /// Clears all explicitly set environment variables and prevents inheriting any parent process
169    /// environment variables.
170    ///
171    /// This method will remove all explicitly added environment variables set via [`LocalCommand::env`]
172    /// or [`LocalCommand::envs`]. In addition, it will prevent the spawned child process from inheriting
173    /// any environment variable from its parent process.
174    ///
175    /// After calling [`LocalCommand::env_clear`], the iterator from [`LocalCommand::get_envs`] will be
176    /// empty.
177    ///
178    /// You can use [`LocalCommand::env_remove`] to clear a single mapping.
179    pub fn env_clear(mut self) -> Self {
180        self.command.env_clear();
181        self
182    }
183
184    /// Sets the working directory for the child process.
185    ///
186    /// # Platform-specific behavior
187    ///
188    /// If the program path is relative (e.g., `"./script.sh"`), it's ambiguous
189    /// whether it should be interpreted relative to the parent's working
190    /// directory or relative to `current_dir`. The behavior in this case is
191    /// platform specific and unstable, and it's recommended to use
192    /// [`canonicalize`] to get an absolute program path instead.
193    ///
194    /// [`canonicalize`]: std::fs::canonicalize
195    pub fn current_dir<P: AsRef<Path>>(mut self, dir: P) -> Self {
196        self.command.current_dir(dir);
197        self
198    }
199
200    /// Returns the path to the program that was given to [`LocalCommand::new`].
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// use bevy_local_commands::LocalCommand;
206    ///
207    /// let cmd = LocalCommand::new("echo");
208    /// assert_eq!(cmd.get_program(), "echo");
209    /// ```
210    pub fn get_program(&self) -> &OsStr {
211        self.command.get_program()
212    }
213
214    /// Returns an iterator of the arguments that will be passed to the program.
215    ///
216    /// This does not include the path to the program as the first argument;
217    /// it only includes the arguments specified with [`LocalCommand::arg`] and
218    /// [`LocalCommand::args`].
219    ///
220    /// # Examples
221    ///
222    /// ```
223    /// use std::ffi::OsStr;
224    /// use bevy_local_commands::LocalCommand;
225    ///
226    /// let mut cmd = LocalCommand::new("echo").arg("first").arg("second");
227    /// let args: Vec<&OsStr> = cmd.get_args().collect();
228    /// assert_eq!(args, &["first", "second"]);
229    /// ```
230    pub fn get_args(&self) -> CommandArgs<'_> {
231        self.command.get_args()
232    }
233
234    /// Returns an iterator of the environment variables explicitly set for the child process.
235    ///
236    /// Environment variables explicitly set using [`LocalCommand::env`], [`LocalCommand::envs`], and
237    /// [`LocalCommand::env_remove`] can be retrieved with this method.
238    ///
239    /// Note that this output does not include environment variables inherited from the parent
240    /// process.
241    ///
242    /// Each element is a tuple key/value pair `(&OsStr, Option<&OsStr>)`. A [`None`] value
243    /// indicates its key was explicitly removed via [`LocalCommand::env_remove`]. The associated key for
244    /// the [`None`] value will no longer inherit from its parent process.
245    ///
246    /// An empty iterator can indicate that no explicit mappings were added or that
247    /// [`LocalCommand::env_clear`] was called. After calling [`LocalCommand::env_clear`], the child process
248    /// will not inherit any environment variables from its parent process.
249    ///
250    /// # Examples
251    ///
252    /// ```
253    /// use std::ffi::OsStr;
254    /// use bevy_local_commands::LocalCommand;
255    ///
256    /// let mut cmd = LocalCommand::new("ls").env("TERM", "dumb").env_remove("TZ");
257    /// let envs: Vec<(&OsStr, Option<&OsStr>)> = cmd.get_envs().collect();
258    /// assert_eq!(envs, &[
259    ///     (OsStr::new("TERM"), Some(OsStr::new("dumb"))),
260    ///     (OsStr::new("TZ"), None)
261    /// ]);
262    /// ```
263    pub fn get_envs(&self) -> CommandEnvs<'_> {
264        self.command.get_envs()
265    }
266
267    /// Returns the working directory for the child process.
268    ///
269    /// This returns [`None`] if the working directory will not be changed.
270    ///
271    /// # Examples
272    ///
273    /// ```
274    /// use std::path::Path;
275    /// use bevy_local_commands::LocalCommand;
276    ///
277    /// let mut cmd = LocalCommand::new("ls");
278    /// assert_eq!(cmd.get_current_dir(), None);
279    /// cmd = cmd.current_dir("/bin");
280    /// assert_eq!(cmd.get_current_dir(), Some(Path::new("/bin")));
281    /// ```
282    pub fn get_current_dir(&self) -> Option<&Path> {
283        self.command.get_current_dir()
284    }
285}
286
287impl From<Command> for LocalCommand {
288    fn from(command: Command) -> Self {
289        Self {
290            command,
291            delay: None,
292            state: LocalCommandState::Ready,
293        }
294    }
295}
296
297impl From<LocalCommand> for Command {
298    fn from(value: LocalCommand) -> Self {
299        value.command
300    }
301}
302
303impl Debug for LocalCommand {
304    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305        self.command.fmt(f)
306    }
307}