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}