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}