tmux_interface/commands/
tmux.rs

1use crate::commands::constants::*;
2use crate::{Error, TmuxCommand, TmuxCommands, TmuxOutput};
3use std::borrow::Cow;
4use std::process::{Child, Command, ExitStatus, Stdio};
5
6/// enum for setting stdin, stdout, stderr, used in [`Tmux`] struct,
7/// wrapper for [`Stdio`][`std::process::Stdio`]
8// NOTE: std::process::Stdio has no Clone, Copy, there is no option to hold them
9// in some wrapper struct, instead they will be initialized in-place before
10// returning std::process::Command back from builder / converter
11#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
12pub enum StdIO {
13    /// inherit from invoker process
14    #[default]
15    Inherit,
16    /// analogous to `/dev/null`
17    Null,
18    /// pipe, used later as child process handle will be returned
19    Piped,
20}
21
22impl From<StdIO> for Stdio {
23    fn from(item: StdIO) -> Self {
24        match item {
25            StdIO::Inherit => Stdio::inherit(),
26            StdIO::Piped => Stdio::piped(),
27            StdIO::Null => Stdio::null(),
28        }
29    }
30}
31
32// XXX: set_cmds_separator
33// NOTE: [-N] missing in man
34/// [man tmux](http://man7.org/linux/man-pages/man1/tmux.1.html#DESCRIPTION)
35///
36/// # Manual
37///
38/// tmux ^3.2:
39/// ```text
40/// tmux [-2CDluvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [-T features] [command [flags]]
41/// ```
42///
43/// tmux ^2.1:
44/// ```text
45/// tmux [-2CluvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
46/// ```
47///
48/// tmux ^1.9:
49/// ```text
50/// tmux [-2lCquvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
51/// ```
52///
53/// tmux ^1.8:
54/// ```text
55/// tmux [-28lCquvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]
56/// ```
57///
58/// tmux ^1.4:
59/// ```text
60/// tmux [-28lquvV] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
61/// ```
62///
63/// tmux ^1.1:
64/// ```text
65/// tmux [-28lquv] [-c shell-command] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
66/// ```
67///
68/// tmux ^1.0:
69/// ```text
70/// tmux [-28dlqUuv] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
71/// ```
72///
73/// tmux ^0.9:
74/// ```text
75/// tmux [-28dqUuv] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
76/// ```
77///
78/// tmux ^0.8:
79/// ```text
80/// tmux [-28dqUuVv] [-f file] [-L socket-name] [-S socket-path] [command [flags]]
81/// ```
82// XXX: using environment vars
83#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
84pub struct Tmux<'a> {
85    /// `[-2]` - Force tmux to assume the terminal supports 256 colours
86    #[cfg(feature = "tmux_0_8")]
87    pub colours256: bool,
88
89    /// `[-8]` - indicates that tmux supports 88 colours
90    #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_9")))]
91    pub colours88: bool,
92
93    /// `[-d]` - indicates that tmux supports defaults colours
94    #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
95    pub default_colours: bool,
96
97    /// `[-q]` - prevent the server sending various information messages
98    #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_2_1")))]
99    pub prevent_msg: bool,
100
101    /// `[-C]` - Start in control mode
102    #[cfg(feature = "tmux_1_8")]
103    pub control_mode: bool,
104
105    /// `[-CC]` - Disable echo
106    #[cfg(feature = "tmux_1_8")]
107    pub disable_echo: bool,
108
109    /// `[-D]` - Do not start the tmux server as a daemon. This also turns the exit-empty option off.  With -D, command may not be specified.
110    #[cfg(feature = "tmux_3_2")]
111    pub no_daemon: bool,
112
113    /// `[-l]` - Behave as a login shell
114    #[cfg(feature = "tmux_1_0")]
115    pub login_shell: bool,
116
117    /// `[-N]` - Do not start the server even if the command would normally do so (for example new-session or start-server).
118    #[cfg(feature = "tmux_3_2")]
119    pub no_start: bool,
120
121    /// `[-U]` - Unlock the server
122    #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
123    pub unlock: bool,
124
125    /// `[-u]` - Write UTF-8 output to the terminal
126    #[cfg(feature = "tmux_0_8")]
127    pub force_utf8: bool,
128
129    /// `[-v]` - Request verbose logging
130    #[cfg(feature = "tmux_0_8")]
131    pub verbose_logging: bool,
132
133    /// `[-V]` - Report the tmux version
134    #[cfg(feature = "tmux_0_8")]
135    pub version: bool,
136
137    /// `[-c shell-command]` - Execute shell-command using the default shell
138    #[cfg(feature = "tmux_1_1")]
139    pub shell_command: Option<Cow<'a, str>>,
140
141    /// `[-f file]` - Specify an alternative configuration file
142    #[cfg(feature = "tmux_0_8")]
143    pub file: Option<Cow<'a, str>>,
144
145    /// `[-L socket-name]` - Allow a different socket name to be specified
146    #[cfg(feature = "tmux_0_8")]
147    pub socket_name: Option<Cow<'a, str>>,
148
149    /// `[-S socket-path]` - Specify a full alternative path to the server socket
150    #[cfg(feature = "tmux_0_8")]
151    pub socket_path: Option<Cow<'a, str>>,
152
153    /// `[-T features]` - Set terminal features for the client
154    #[cfg(feature = "tmux_3_2")]
155    pub features: Option<Cow<'a, str>>,
156
157    /// `[command]`
158    pub command: Option<TmuxCommands<'a>>,
159
160    /// (1)
161    pub stdin: Option<StdIO>,
162
163    /// (2)
164    pub stdout: Option<StdIO>,
165
166    /// (3)
167    pub stderr: Option<StdIO>,
168}
169
170impl<'a> Tmux<'a> {
171    pub fn new() -> Self {
172        Default::default()
173    }
174
175    /// `[-2]` - Force tmux to assume the terminal supports 256 colours
176    #[cfg(feature = "tmux_0_8")]
177    pub fn colours256(mut self) -> Self {
178        self.colours256 = true;
179        self
180    }
181
182    /// `[-8]` - indicates that tmux supports 88 colours
183    #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_9")))]
184    pub fn colours88(mut self) -> Self {
185        self.colours88 = true;
186        self
187    }
188
189    /// `[-d]` - indicates that tmux supports defaults colours
190    #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
191    pub fn default_colours(mut self) -> Self {
192        self.default_colours = true;
193        self
194    }
195
196    /// `[-q]` - prevent the server sending various information messages
197    #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_2_1")))]
198    pub fn prevent_msg(mut self) -> Self {
199        self.prevent_msg = true;
200        self
201    }
202
203    /// `[-C]` - Start in control mode
204    #[cfg(feature = "tmux_1_8")]
205    pub fn control_mode(mut self) -> Self {
206        self.control_mode = true;
207        self
208    }
209
210    /// `[-CC]` - Disable echo
211    #[cfg(feature = "tmux_1_8")]
212    pub fn disable_echo(mut self) -> Self {
213        self.disable_echo = true;
214        self
215    }
216
217    /// `[-D]` - Do not start the tmux server as a daemon. This also turns the exit-empty option off.  With -D, command may not be specified.
218    #[cfg(feature = "tmux_3_2")]
219    pub fn no_daemon(mut self) -> Self {
220        self.no_daemon = true;
221        self
222    }
223
224    /// `[-l]` - Behave as a login shell
225    #[cfg(feature = "tmux_1_0")]
226    pub fn login_shell(mut self) -> Self {
227        self.login_shell = true;
228        self
229    }
230
231    /// `[-N]` - Do not start the server even if the command would normally do so (for example new-session or start-server).
232    #[cfg(feature = "tmux_3_2")]
233    pub fn no_start(mut self) -> Self {
234        self.no_start = true;
235        self
236    }
237
238    /// `[-U]` - Unlock the server
239    #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
240    pub fn unlock(mut self) -> Self {
241        self.unlock = true;
242        self
243    }
244
245    /// `[-u]` - Write UTF-8 output to the terminal
246    #[cfg(feature = "tmux_0_8")]
247    pub fn force_utf8(mut self) -> Self {
248        self.force_utf8 = true;
249        self
250    }
251
252    /// `[-v]` - Request verbose logging
253    #[cfg(feature = "tmux_0_8")]
254    pub fn verbose_logging(mut self) -> Self {
255        self.verbose_logging = true;
256        self
257    }
258
259    /// `[-V]` - Report the tmux version
260    #[cfg(feature = "tmux_0_8")]
261    pub fn version(mut self) -> Self {
262        self.version = true;
263        self
264    }
265
266    /// `[-c shell-command]` - Execute shell-command using the default shell
267    #[cfg(feature = "tmux_1_1")]
268    pub fn shell_command<S: Into<Cow<'a, str>>>(mut self, shell_command: S) -> Self {
269        self.shell_command = Some(shell_command.into());
270        self
271    }
272
273    /// `[-f file]` - Specify an alternative configuration file
274    #[cfg(feature = "tmux_0_8")]
275    pub fn file<S: Into<Cow<'a, str>>>(mut self, file: S) -> Self {
276        self.file = Some(file.into());
277        self
278    }
279
280    /// `[-L socket-name]` - Allow a different socket name to be specified
281    #[cfg(feature = "tmux_0_8")]
282    pub fn socket_name<S: Into<Cow<'a, str>>>(mut self, socket_name: S) -> Self {
283        self.socket_name = Some(socket_name.into());
284        self
285    }
286
287    /// `[-S socket-path]` - Specify a full alternative path to the server socket
288    #[cfg(feature = "tmux_0_8")]
289    pub fn socket_path<S: Into<Cow<'a, str>>>(mut self, socket_path: S) -> Self {
290        self.socket_path = Some(socket_path.into());
291        self
292    }
293
294    /// `[-T features]` - Set terminal features for the client
295    #[cfg(feature = "tmux_3_2")]
296    pub fn features<S: Into<Cow<'a, str>>>(mut self, features: S) -> Self {
297        self.features = Some(features.into());
298        self
299    }
300
301    /// `[command]`
302    pub fn command<T: Into<TmuxCommand<'a>>>(mut self, command: T) -> Self {
303        self.command
304            .get_or_insert(TmuxCommands::new())
305            .push(command.into());
306        self
307    }
308
309    pub fn build(self) -> TmuxCommand<'a> {
310        let mut cmd = TmuxCommand::new();
311
312        cmd.name(TMUX);
313
314        // `[-2]` - Force tmux to assume the terminal supports 256 colours
315        #[cfg(feature = "tmux_0_8")]
316        if self.colours256 {
317            cmd.push_flag(_2_KEY);
318        }
319
320        // `[-8]` - indicates that tmux supports 88 colours
321        #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_9")))]
322        if self.colours88 {
323            cmd.push_flag(_8_KEY);
324        }
325
326        // `[-d]` - indicates that tmux supports defaults colours
327        #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
328        if self.default_colours {
329            cmd.push_flag(D_LOWERCASE_KEY);
330        }
331
332        // `[-q]` - prevent the server sending various information messages
333        #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_2_1")))]
334        if self.prevent_msg {
335            cmd.push_flag(Q_LOWERCASE_KEY);
336        }
337
338        // `[-C]` - Start in control mode
339        #[cfg(feature = "tmux_1_8")]
340        if self.control_mode {
341            cmd.push_flag(C_UPPERCASE_KEY);
342        }
343
344        // `[-CC]` - Disable echo
345        #[cfg(feature = "tmux_1_8")]
346        if self.disable_echo {
347            cmd.push_flag(CC_UPPERCASE_KEY);
348        }
349
350        // `[-D]` - Do not start the tmux server as a daemon. This also turns the exit-empty option off.  With -D, command may not be specified.
351        #[cfg(feature = "tmux_3_2")]
352        if self.no_daemon {
353            cmd.push_flag(D_UPPERCASE_KEY);
354        }
355
356        // `[-l]` - Behave as a login shell
357        #[cfg(feature = "tmux_1_0")]
358        if self.login_shell {
359            cmd.push_flag(L_LOWERCASE_KEY);
360        }
361
362        // `[-N]` - Do not start the server even if the command would normally do so (for example new-session or start-server).
363        #[cfg(feature = "tmux_3_2")]
364        if self.no_start {
365            cmd.push_flag(N_UPPERCASE_KEY);
366        }
367
368        // `[-U]` - Unlock the server
369        #[cfg(all(feature = "tmux_0_8", not(feature = "tmux_1_1")))]
370        if self.unlock {
371            cmd.push_flag(U_UPPERCASE_KEY);
372        }
373
374        // `[-u]` - Write UTF-8 output to the terminal
375        #[cfg(feature = "tmux_0_8")]
376        if self.force_utf8 {
377            cmd.push_flag(U_LOWERCASE_KEY);
378        }
379
380        // `[-v]` - Request verbose logging
381        #[cfg(feature = "tmux_0_8")]
382        if self.verbose_logging {
383            cmd.push_flag(V_LOWERCASE_KEY);
384        }
385
386        // `[-V]` - Report the tmux version
387        #[cfg(feature = "tmux_0_8")]
388        if self.version {
389            cmd.push_flag(V_UPPERCASE_KEY);
390        }
391
392        // `[-c shell-command]` - Execute shell-command using the default shell
393        #[cfg(feature = "tmux_1_1")]
394        if let Some(shell_command) = self.shell_command {
395            cmd.push_option(C_LOWERCASE_KEY, shell_command);
396        }
397
398        // `[-f file]` - Specify an alternative configuration file
399        #[cfg(feature = "tmux_0_8")]
400        if let Some(file) = self.file {
401            cmd.push_option(F_LOWERCASE_KEY, file);
402        }
403
404        // `[-L socket-name]` - Allow a different socket name to be specified
405        #[cfg(feature = "tmux_0_8")]
406        if let Some(socket_name) = self.socket_name {
407            cmd.push_option(L_UPPERCASE_KEY, socket_name);
408        }
409
410        // `[-S socket-path]` - Specify a full alternative path to the server socket
411        #[cfg(feature = "tmux_0_8")]
412        if let Some(socket_path) = self.socket_path {
413            cmd.push_option(S_UPPERCASE_KEY, socket_path);
414        }
415
416        // `[-T features]` - Set terminal features for the client
417        #[cfg(feature = "tmux_3_2")]
418        if let Some(features) = self.features {
419            cmd.push_option(T_UPPERCASE_KEY, features);
420        }
421
422        // `[command]`
423        if let Some(command) = self.command {
424            cmd.push_cmds(command);
425        }
426
427        cmd
428    }
429
430    //pub fn to_command(&self) -> Command {
431    //Command::from(self)
432    //}
433
434    // XXX: ?
435    pub fn into_command(self) -> Command {
436        Command::from(self)
437    }
438
439    // XXX: command or subcommand better name?
440    pub fn with_command<T: Into<TmuxCommand<'a>>>(command: T) -> Self {
441        Tmux::new().command(command.into())
442    }
443
444    pub fn with_commands(commands: TmuxCommands<'a>) -> Self {
445        Tmux::new().commands(commands)
446    }
447
448    // XXX: ?
449    pub fn add_command<T: Into<TmuxCommand<'a>>>(mut self, command: T) -> Self {
450        self.command
451            .get_or_insert(TmuxCommands::new())
452            .push(command.into());
453        self
454    }
455
456    pub fn commands(mut self, commands: TmuxCommands<'a>) -> Self {
457        self.command = Some(commands);
458        self
459    }
460}
461
462impl<'a> Tmux<'a> {
463    /// execute tmux process, wait for output, return output
464    ///
465    /// by default:
466    ///  * stdin  is inherited (differs from [`std::process::Command`], where it
467    ///    is not inherited by default) prevents from immediately closing tmux
468    ///    after attempt to read from stdin
469    ///  * stdout is inherited
470    ///  * stderr is inherited
471    pub fn output(self) -> Result<TmuxOutput, Error> {
472        let mut command = Command::from(self);
473        let output = command.output()?;
474        Ok(TmuxOutput(output))
475    }
476
477    /// spawn tmux process, return process handle as child
478    /// by default:
479    ///  * stdin  is inherited
480    ///  * stdout is inherited
481    ///  * stderr is inherited
482    pub fn spawn(self) -> Result<Child, Error> {
483        let mut command = Command::from(self);
484        let child = command.spawn()?;
485        Ok(child)
486    }
487
488    /// spawn tmux process, return status result of the command execution
489    /// by default:
490    ///  * stdin  is inherited
491    ///  * stdout is inherited
492    ///  * stderr is inherited
493    pub fn status(self) -> Result<ExitStatus, Error> {
494        let mut command = Command::from(self);
495        let status = command.status()?;
496        Ok(status)
497    }
498
499    /// set stdin
500    pub fn stdin(mut self, stdin: Option<StdIO>) -> Self {
501        self.stdin = stdin;
502        self
503    }
504
505    /// set stdout
506    pub fn stdout(mut self, stdout: Option<StdIO>) -> Self {
507        self.stdout = stdout;
508        self
509    }
510
511    /// set stderr
512    pub fn stderr(mut self, stderr: Option<StdIO>) -> Self {
513        self.stderr = stderr;
514        self
515    }
516}
517
518//use crate::{HasSession, KillSession, NewSession};
519
520//impl<'a> From<NewSession<'a>> for Tmux<'a> {
521//fn from(item: NewSession<'a>) -> Self {
522//item.build().into_tmux()
523//}
524//}
525
526//impl<'a> From<HasSession<'a>> for Tmux<'a> {
527//fn from(item: HasSession<'a>) -> Self {
528//item.build().into_tmux()
529//}
530//}
531
532//impl<'a> From<KillSession<'a>> for Tmux<'a> {
533//fn from(item: KillSession<'a>) -> Self {
534//item.build().into_tmux()
535//}
536//}
537
538// convert from Tmux into Command (take Tmux -> build TmuxCommand -> into Command)
539impl<'a> From<Tmux<'a>> for Command {
540    fn from(item: Tmux<'a>) -> Self {
541        let mut command: Command = item.clone().build().into();
542
543        if let Some(stdio) = item.stdin {
544            command.stdin::<Stdio>(stdio.into());
545        } else {
546            // NOTE: inherit stdin to prevent tmux fail with error `terminal failed: not a terminal`
547            command.stdin(Stdio::inherit());
548        }
549
550        if let Some(stdio) = item.stdout {
551            command.stdout::<Stdio>(stdio.into());
552        }
553
554        if let Some(stdio) = item.stderr {
555            command.stderr::<Stdio>(stdio.into());
556        }
557
558        command
559    }
560}
561
562//impl<'a> From<&Tmux<'a>> for Command {
563//fn from(tmux: &Tmux) -> Self {
564////let mut command = Command::new(TMUX);
565
566//let cmd = match &tmux.0.name {
567//Some(cmd) => cmd,
568//None => &Cow::Borrowed(TMUX),
569//};
570//let mut command = Command::new(cmd.as_ref());
571
572////// XXX: ugly?
573////if let Some(v) = &tmux.bin_args {
574////for a in v {
575////command.arg(a.as_ref());
576////}
577////}
578
579//// XXX: ugly?
580//if let Some(v) = &tmux.0.args {
581//for a in v {
582//command.arg(a.as_ref());
583//}
584//}
585
586//command
587//}
588//}