1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Command {
10 Load,
11 Play,
12 Pause,
13 Quit,
14 Terminate,
15 MainView,
16 Next,
17 Prev,
18 Goto,
19 Label,
20 Loop,
21 ClearLoop,
22 Volume,
23 VolumeUp,
24 VolumeDown,
25 Mute,
26 Unmute,
27 Gain,
28 Pitch,
29 Cut,
30 Rec,
31 Mic,
32 Save,
33 Playlist,
34 PlaylistTop,
35 PlaylistBottom,
36 PlaylistUp,
37 PlaylistDown,
38 Webradio,
39 Scope,
40 Spectro,
41 Transcribe,
42 Help,
43}
44
45impl Command {
46 pub const fn name(self) -> &'static str {
48 match self {
49 Self::Load => "load",
50 Self::Play => "play",
51 Self::Pause => "pause",
52 Self::Quit => "quit",
53 Self::Terminate => "terminate",
54 Self::MainView => "mainview",
55 Self::Next => "next",
56 Self::Prev => "prev",
57 Self::Goto => "goto",
58 Self::Label => "label",
59 Self::Loop => "loop",
60 Self::ClearLoop => "clearloop",
61 Self::Volume => "volume",
62 Self::VolumeUp => "+",
63 Self::VolumeDown => "-",
64 Self::Mute => "mute",
65 Self::Unmute => "unmute",
66 Self::Gain => "gain",
67 Self::Pitch => "pitch",
68 Self::Cut => "cut",
69 Self::Rec => "rec",
70 Self::Mic => "mic",
71 Self::Save => "save",
72 Self::Playlist => "playlist",
73 Self::PlaylistTop => "top",
74 Self::PlaylistBottom => "bottom",
75 Self::PlaylistUp => "up",
76 Self::PlaylistDown => "down",
77 Self::Webradio => "webradio",
78 Self::Scope => "scope",
79 Self::Spectro => "spectro",
80 Self::Transcribe => "transcribe",
81 Self::Help => "help",
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy)]
88pub struct CommandSpec {
89 pub command: Command,
91 pub aliases: &'static [&'static str],
93}
94
95pub const COMMAND_SPECS: &[CommandSpec] = &[
97 CommandSpec {
98 command: Command::Load,
99 aliases: &["l", "open"],
100 },
101 CommandSpec {
102 command: Command::Play,
103 aliases: &["p", "▶"],
104 },
105 CommandSpec {
106 command: Command::Pause,
107 aliases: &["a", "stop", "⏸", "⏹"],
108 },
109 CommandSpec {
110 command: Command::Quit,
111 aliases: &["q"],
112 },
113 CommandSpec {
114 command: Command::Terminate,
115 aliases: &["shutdown", "x", "leave", "exit"],
116 },
117 CommandSpec {
118 command: Command::MainView,
119 aliases: &["main", "player", "back", "close"],
120 },
121 CommandSpec {
122 command: Command::Next,
123 aliases: &["n"],
124 },
125 CommandSpec {
126 command: Command::Prev,
127 aliases: &["v", "previous"],
128 },
129 CommandSpec {
130 command: Command::Goto,
131 aliases: &["g", "seek"],
132 },
133 CommandSpec {
134 command: Command::Label,
135 aliases: &["e"],
136 },
137 CommandSpec {
138 command: Command::Loop,
139 aliases: &[],
140 },
141 CommandSpec {
142 command: Command::ClearLoop,
143 aliases: &["unloop"],
144 },
145 CommandSpec {
146 command: Command::Volume,
147 aliases: &["vol"],
148 },
149 CommandSpec {
150 command: Command::VolumeUp,
151 aliases: &[],
152 },
153 CommandSpec {
154 command: Command::VolumeDown,
155 aliases: &[],
156 },
157 CommandSpec {
158 command: Command::Mute,
159 aliases: &["m"],
160 },
161 CommandSpec {
162 command: Command::Unmute,
163 aliases: &["u"],
164 },
165 CommandSpec {
166 command: Command::Gain,
167 aliases: &[],
168 },
169 CommandSpec {
170 command: Command::Pitch,
171 aliases: &["t"],
172 },
173 CommandSpec {
174 command: Command::Cut,
175 aliases: &["c"],
176 },
177 CommandSpec {
178 command: Command::Rec,
179 aliases: &["r", "record"],
180 },
181 CommandSpec {
182 command: Command::Mic,
183 aliases: &["i"],
184 },
185 CommandSpec {
186 command: Command::Save,
187 aliases: &["s"],
188 },
189 CommandSpec {
190 command: Command::Playlist,
191 aliases: &["y"],
192 },
193 CommandSpec {
194 command: Command::PlaylistTop,
195 aliases: &[],
196 },
197 CommandSpec {
198 command: Command::PlaylistBottom,
199 aliases: &[],
200 },
201 CommandSpec {
202 command: Command::PlaylistUp,
203 aliases: &[],
204 },
205 CommandSpec {
206 command: Command::PlaylistDown,
207 aliases: &[],
208 },
209 CommandSpec {
210 command: Command::Webradio,
211 aliases: &["radio", "w"],
212 },
213 CommandSpec {
214 command: Command::Scope,
215 aliases: &["o", "oscillo", "oscilloscope"],
216 },
217 CommandSpec {
218 command: Command::Spectro,
219 aliases: &["spectroscope"],
220 },
221 CommandSpec {
222 command: Command::Transcribe,
223 aliases: &[],
224 },
225 CommandSpec {
226 command: Command::Help,
227 aliases: &["?"],
228 },
229];
230
231#[derive(Debug, Clone)]
233pub struct ParsedCommand {
234 pub command: Command,
236 #[allow(dead_code)]
237 pub raw: String,
238 #[allow(dead_code)]
239 pub raw_action: String,
240 pub args: Vec<String>,
242}
243
244impl ParsedCommand {
245 pub fn arg(&self, index: usize) -> Option<&str> {
247 self.args.get(index).map(String::as_str)
248 }
249
250 pub fn rest(&self) -> String {
252 self.args.join(" ")
253 }
254}
255
256#[derive(Debug, Clone, PartialEq, Eq)]
258pub enum ParseCommandError {
259 Empty,
261 Unknown(String),
263 Ambiguous { input: String, matches: Vec<String> },
265}
266
267pub fn parse_command(input: &str) -> Result<ParsedCommand, ParseCommandError> {
269 let raw = input.trim();
270 if raw.is_empty() {
271 return Err(ParseCommandError::Empty);
272 }
273
274 let mut parts = raw.split_whitespace();
275 let raw_action = parts.next().unwrap_or_default().to_lowercase();
276 let args = parts.map(ToOwned::to_owned).collect::<Vec<_>>();
277 let command = resolve_command(raw_action.as_str())?;
278
279 Ok(ParsedCommand {
280 command,
281 raw: raw.to_owned(),
282 raw_action,
283 args,
284 })
285}
286
287pub fn command_names() -> String {
289 COMMAND_SPECS
290 .iter()
291 .map(|spec| spec.command.name())
292 .collect::<Vec<_>>()
293 .join("/")
294}
295
296fn resolve_command(action: &str) -> Result<Command, ParseCommandError> {
297 if let Some(spec) = COMMAND_SPECS.iter().find(|spec| {
298 spec.command.name() == action || spec.aliases.iter().any(|alias| *alias == action)
299 }) {
300 return Ok(spec.command);
301 }
302
303 if action.chars().count() <= 2 && action.chars().all(|ch| ch.is_ascii_alphanumeric()) {
304 let matches = COMMAND_SPECS
305 .iter()
306 .filter(|spec| spec.command.name().starts_with(action))
307 .map(|spec| spec.command)
308 .collect::<Vec<_>>();
309
310 return match matches.as_slice() {
311 [] => Err(ParseCommandError::Unknown(action.to_owned())),
312 [command] => Ok(*command),
313 _ => Err(ParseCommandError::Ambiguous {
314 input: action.to_owned(),
315 matches: matches
316 .into_iter()
317 .map(Command::name)
318 .map(ToOwned::to_owned)
319 .collect(),
320 }),
321 };
322 }
323
324 Err(ParseCommandError::Unknown(action.to_owned()))
325}
326
327pub trait ControlEventHandler {
329 fn on_load(&mut self, _event: &ParsedCommand) {}
330 fn on_play(&mut self, _event: &ParsedCommand) {}
331 fn on_pause(&mut self, _event: &ParsedCommand) {}
332 fn on_quit(&mut self, _event: &ParsedCommand) {}
333 fn on_terminate(&mut self, _event: &ParsedCommand) {}
334 fn on_mainview(&mut self, _event: &ParsedCommand) {}
335 fn on_next(&mut self, _event: &ParsedCommand) {}
336 fn on_prev(&mut self, _event: &ParsedCommand) {}
337 fn on_goto(&mut self, _event: &ParsedCommand) {}
338 fn on_label(&mut self, _event: &ParsedCommand) {}
339 fn on_loop(&mut self, _event: &ParsedCommand) {}
340 fn on_clear_loop(&mut self, _event: &ParsedCommand) {}
341 fn on_volume(&mut self, _event: &ParsedCommand) {}
342 fn on_volume_up(&mut self, _event: &ParsedCommand) {}
343 fn on_volume_down(&mut self, _event: &ParsedCommand) {}
344 fn on_mute(&mut self, _event: &ParsedCommand) {}
345 fn on_unmute(&mut self, _event: &ParsedCommand) {}
346 fn on_gain(&mut self, _event: &ParsedCommand) {}
347 fn on_pitch(&mut self, _event: &ParsedCommand) {}
348 fn on_cut(&mut self, _event: &ParsedCommand) {}
349 fn on_rec(&mut self, _event: &ParsedCommand) {}
350 fn on_mic(&mut self, _event: &ParsedCommand) {}
351 fn on_save(&mut self, _event: &ParsedCommand) {}
352 fn on_playlist(&mut self, _event: &ParsedCommand) {}
353 fn on_playlist_top(&mut self, _event: &ParsedCommand) {}
354 fn on_playlist_bottom(&mut self, _event: &ParsedCommand) {}
355 fn on_playlist_up(&mut self, _event: &ParsedCommand) {}
356 fn on_playlist_down(&mut self, _event: &ParsedCommand) {}
357 fn on_webradio(&mut self, _event: &ParsedCommand) {}
358 fn on_scope(&mut self, _event: &ParsedCommand) {}
359 fn on_spectro(&mut self, _event: &ParsedCommand) {}
360 fn on_transcribe(&mut self, _event: &ParsedCommand) {}
361 fn on_help(&mut self, _event: &ParsedCommand) {}
362}
363
364pub fn dispatch(handler: &mut impl ControlEventHandler, event: &ParsedCommand) {
366 match event.command {
367 Command::Load => handler.on_load(event),
368 Command::Play => handler.on_play(event),
369 Command::Pause => handler.on_pause(event),
370 Command::Quit => handler.on_quit(event),
371 Command::Terminate => handler.on_terminate(event),
372 Command::MainView => handler.on_mainview(event),
373 Command::Next => handler.on_next(event),
374 Command::Prev => handler.on_prev(event),
375 Command::Goto => handler.on_goto(event),
376 Command::Label => handler.on_label(event),
377 Command::Loop => handler.on_loop(event),
378 Command::ClearLoop => handler.on_clear_loop(event),
379 Command::Volume => handler.on_volume(event),
380 Command::VolumeUp => handler.on_volume_up(event),
381 Command::VolumeDown => handler.on_volume_down(event),
382 Command::Mute => handler.on_mute(event),
383 Command::Unmute => handler.on_unmute(event),
384 Command::Gain => handler.on_gain(event),
385 Command::Pitch => handler.on_pitch(event),
386 Command::Cut => handler.on_cut(event),
387 Command::Rec => handler.on_rec(event),
388 Command::Mic => handler.on_mic(event),
389 Command::Save => handler.on_save(event),
390 Command::Playlist => handler.on_playlist(event),
391 Command::PlaylistTop => handler.on_playlist_top(event),
392 Command::PlaylistBottom => handler.on_playlist_bottom(event),
393 Command::PlaylistUp => handler.on_playlist_up(event),
394 Command::PlaylistDown => handler.on_playlist_down(event),
395 Command::Webradio => handler.on_webradio(event),
396 Command::Scope => handler.on_scope(event),
397 Command::Spectro => handler.on_spectro(event),
398 Command::Transcribe => handler.on_transcribe(event),
399 Command::Help => handler.on_help(event),
400 }
401}