tmux_layout/
cli.rs

1use clap::{Arg, ArgAction, ArgMatches, Command};
2
3use crate::tmux::QueryScope;
4
5#[derive(Debug)]
6pub enum Subcommand<'a> {
7    Create(CreateOpts<'a>),
8    Export(ExportOpts<'a>),
9    DumpCommand(DumpCommandOps<'a>),
10    DumpConfig(DumpConfigOps<'a>),
11}
12
13impl Subcommand<'_> {
14    pub fn from_matches(matches: &ArgMatches) -> Option<Subcommand<'_>> {
15        match matches.subcommand() {
16            None => None,
17            Some(("create", sub_matches)) => {
18                Some(Subcommand::Create(CreateOpts::from_matches(sub_matches)))
19            }
20            Some(("dump-command", sub_matches)) => Some(Subcommand::DumpCommand(
21                DumpCommandOps::from_matches(sub_matches),
22            )),
23            Some(("dump-config", sub_matches)) => Some(Subcommand::DumpConfig(
24                DumpConfigOps::from_matches(sub_matches),
25            )),
26            Some(("export", sub_matches)) => {
27                Some(Subcommand::Export(ExportOpts::from_matches(sub_matches)))
28            }
29            _ => unreachable!("undefined subcommand"),
30        }
31    }
32}
33
34#[derive(Debug)]
35pub struct CreateOpts<'a> {
36    pub config_path: Option<&'a str>,
37    pub session_select_mode: SessionSelectModeOption,
38    pub ignore_existing_sessions: bool,
39    pub tmux_args: Vec<&'a str>,
40}
41
42impl CreateOpts<'_> {
43    fn from_matches(matches: &ArgMatches) -> CreateOpts<'_> {
44        CreateOpts {
45            config_path: matches.get_one::<String>("config").map(|s| s.as_str()),
46            session_select_mode: SessionSelectModeOption::from_arg(
47                matches
48                    .get_one::<String>("session-select-mode")
49                    .map(|s| s.as_str()),
50            ),
51            ignore_existing_sessions: matches.get_flag("ignore-existing-sessions"),
52            tmux_args: matches
53                .get_many::<String>("tmux args")
54                .into_iter()
55                .flatten()
56                .map(|s| s.as_str())
57                .collect(),
58        }
59    }
60}
61
62#[derive(Debug)]
63pub struct ExportOpts<'a> {
64    pub scope: QueryScope,
65    pub format: ConfigFormat,
66    pub tmux_args: Vec<&'a str>,
67}
68
69impl ExportOpts<'_> {
70    fn from_matches(matches: &ArgMatches) -> ExportOpts<'_> {
71        ExportOpts {
72            scope: QueryScope::from_arg(matches.get_one::<String>("scope").map(|s| s.as_str())),
73            format: ConfigFormat::from_arg(matches.get_one::<String>("format").map(|s| s.as_str())),
74            tmux_args: matches
75                .get_many::<String>("tmux args")
76                .into_iter()
77                .flatten()
78                .map(|s| s.as_str())
79                .collect(),
80        }
81    }
82}
83
84#[derive(Debug)]
85pub struct DumpCommandOps<'a> {
86    pub config_path: Option<&'a str>,
87    pub session_select_mode: SessionSelectModeOption,
88    pub ignore_existing_sessions: bool,
89    pub tmux_args: Vec<&'a str>,
90}
91
92impl DumpCommandOps<'_> {
93    fn from_matches(matches: &ArgMatches) -> DumpCommandOps<'_> {
94        DumpCommandOps {
95            config_path: matches.get_one::<String>("config").map(|s| s.as_str()),
96            session_select_mode: SessionSelectModeOption::from_arg(
97                matches
98                    .get_one::<String>("session-select-mode")
99                    .map(|s| s.as_str()),
100            ),
101            ignore_existing_sessions: matches.get_flag("ignore-existing-sessions"),
102            tmux_args: matches
103                .get_many::<String>("tmux args")
104                .into_iter()
105                .flatten()
106                .map(|s| s.as_str())
107                .collect(),
108        }
109    }
110}
111
112#[derive(Debug)]
113pub struct DumpConfigOps<'a> {
114    pub config_path: Option<&'a str>,
115    pub format: ConfigFormat,
116}
117
118impl DumpConfigOps<'_> {
119    fn from_matches(matches: &ArgMatches) -> DumpConfigOps<'_> {
120        DumpConfigOps {
121            config_path: matches.get_one::<String>("config").map(|s| s.as_str()),
122            format: ConfigFormat::from_arg(matches.get_one::<String>("format").map(|s| s.as_str())),
123        }
124    }
125}
126
127#[derive(Debug, Clone, Copy)]
128pub enum ConfigFormat {
129    Yaml,
130    Toml,
131}
132
133impl ConfigFormat {
134    fn from_arg(arg: Option<&str>) -> ConfigFormat {
135        match arg {
136            Some("yaml") | None => ConfigFormat::Yaml,
137            Some("toml") => ConfigFormat::Toml,
138            _ => unreachable!("undefined ConfigFormat"),
139        }
140    }
141}
142
143impl QueryScope {
144    fn from_arg(arg: Option<&str>) -> QueryScope {
145        match arg {
146            Some("all") => QueryScope::AllSessions,
147            Some("session") => QueryScope::CurrentSession,
148            Some("window") => QueryScope::CurrentWindow,
149            _ => unreachable!("undefined ExportScope"),
150        }
151    }
152}
153
154#[derive(Debug, Clone, Copy, Default)]
155pub enum SessionSelectModeOption {
156    #[default]
157    Auto,
158    Attach,
159    Switch,
160    Detached,
161}
162
163impl SessionSelectModeOption {
164    fn from_arg(arg: Option<&str>) -> SessionSelectModeOption {
165        match arg {
166            Some("auto") | None => SessionSelectModeOption::Auto,
167            Some("attach") => SessionSelectModeOption::Attach,
168            Some("switch") => SessionSelectModeOption::Switch,
169            Some("detached") => SessionSelectModeOption::Detached,
170            _ => unreachable!("undefined AttachOption"),
171        }
172    }
173}
174
175pub fn app() -> Command {
176    let config_arg = Arg::new("config")
177        .help(
178            "Config file path. If not given the config file is searched for at:\n\
179              - ./tmux-layout.{yaml,yml,toml}\n\
180              - ~/tmux-layout.{yaml,yml,toml}\n",
181        )
182        .required(false)
183        .short('c')
184        .long("config")
185        .num_args(1)
186        .value_name("FILE")
187        .required(false);
188
189    let format_arg = Arg::new("format")
190        .help("Export config format")
191        .required(false)
192        .short('f')
193        .long("format")
194        .num_args(1)
195        .value_name("FORMAT")
196        .value_parser(["yaml", "toml"])
197        .default_value("yaml");
198
199    let session_select_mode_arg = Arg::new("session-select-mode")
200        .help(
201            "Session select mode:\n\
202                - switch: switch existing client to selected (or last created) session\n\
203                - attach: attach to selected (or last created) session\n\
204                - detached: don't attach/switch to any session\n\
205                - auto: switch when there is a tmux client, \
206                  attach when running from a TTY, \
207                  detached otherwise\n",
208        )
209        .short('m')
210        .long("session-select-mode")
211        .num_args(1)
212        .value_name("MODE")
213        .value_parser(["auto", "attach", "switch", "detached"])
214        .default_value("auto")
215        .required(false);
216
217    let ignore_existing_sessions_arg = Arg::new("ignore-existing-sessions")
218        .help("Don't create already existing tmux sessions")
219        .short('i')
220        .long("ignore-existing-sessions")
221        .action(ArgAction::SetTrue)
222        .required(false);
223
224    let tmux_args = Arg::new("tmux args")
225        .required(false)
226        .last(true)
227        .num_args(0..);
228
229    Command::new("tmux-layout")
230        .version("0.1.0")
231        .author("Daniel Strittmatter <github@smattr.de>")
232        .about("Starts tmux sessions in pre-defined layouts")
233        .subcommand(
234            Command::new("create")
235                .about("Create tmux layout from config file")
236                .arg(&config_arg)
237                .arg(&session_select_mode_arg)
238                .arg(&ignore_existing_sessions_arg)
239                .arg(&tmux_args),
240        )
241        .subcommand(
242            Command::new("dump-command")
243                .about("Dump tmux command to stdout")
244                .arg(&config_arg)
245                .arg(&session_select_mode_arg)
246                .arg(&ignore_existing_sessions_arg)
247                .arg(&tmux_args),
248        )
249        .subcommand(
250            Command::new("dump-config")
251                .arg(&config_arg)
252                .about("Dump config to stdout")
253                .arg(&format_arg),
254        )
255        .subcommand(
256            Command::new("export")
257                .about("Exports running tmux sessions into tmux-layout config file format")
258                .arg(
259                    Arg::new("scope")
260                        .help("Export scope")
261                        .required(false)
262                        .short('s')
263                        .long("scope")
264                        .num_args(1)
265                        .value_name("SCOPE")
266                        .value_parser(["all", "session", "window"])
267                        .default_value("all"),
268                )
269                .arg(&format_arg)
270                .arg(&tmux_args),
271        )
272}
273
274#[test]
275fn verify_cli() {
276    app().debug_assert();
277}