Fix: don't crash on empty command input
This commit is contained in:
660
src/command.rs
660
src/command.rs
@@ -366,273 +366,274 @@ pub fn parse(input: &str) -> Result<Vec<Command>, CommandParseError> {
|
|||||||
for command_input in command_inputs {
|
for command_input in command_inputs {
|
||||||
let components: Vec<_> = command_input.split_whitespace().collect();
|
let components: Vec<_> = command_input.split_whitespace().collect();
|
||||||
|
|
||||||
let command = handle_aliases(components[0]);
|
if let Some((command, args)) = components.split_first() {
|
||||||
let args = components[1..].to_vec();
|
let command = handle_aliases(command);
|
||||||
|
use CommandParseError::*;
|
||||||
use CommandParseError::*;
|
let command = match command {
|
||||||
let command = match command {
|
"quit" => Command::Quit,
|
||||||
"quit" => Command::Quit,
|
"playpause" => Command::TogglePlay,
|
||||||
"playpause" => Command::TogglePlay,
|
"stop" => Command::Stop,
|
||||||
"stop" => Command::Stop,
|
"previous" => Command::Previous,
|
||||||
"previous" => Command::Previous,
|
"next" => Command::Next,
|
||||||
"next" => Command::Next,
|
"clear" => Command::Clear,
|
||||||
"clear" => Command::Clear,
|
"queue" => Command::Queue,
|
||||||
"queue" => Command::Queue,
|
"playnext" => Command::PlayNext,
|
||||||
"playnext" => Command::PlayNext,
|
"play" => Command::Play,
|
||||||
"play" => Command::Play,
|
"update" => Command::UpdateLibrary,
|
||||||
"update" => Command::UpdateLibrary,
|
"save" => match args.get(0).cloned() {
|
||||||
"save" => match args.get(0).cloned() {
|
Some("queue") => Ok(Command::SaveQueue),
|
||||||
Some("queue") => Ok(Command::SaveQueue),
|
Some(arg) => Err(BadEnumArg {
|
||||||
Some(arg) => Err(BadEnumArg {
|
arg: arg.into(),
|
||||||
arg: arg.into(),
|
accept: vec!["**omit**".into(), "queue".into()],
|
||||||
accept: vec!["**omit**".into(), "queue".into()],
|
}),
|
||||||
}),
|
None => Ok(Command::Save),
|
||||||
None => Ok(Command::Save),
|
}?,
|
||||||
}?,
|
"delete" => Command::Delete,
|
||||||
"delete" => Command::Delete,
|
"focus" => {
|
||||||
"focus" => {
|
let &target = args.get(0).ok_or(InsufficientArgs {
|
||||||
let &target = args.get(0).ok_or(InsufficientArgs {
|
|
||||||
cmd: command.into(),
|
|
||||||
hint: Some("queue|search|library".into()),
|
|
||||||
})?;
|
|
||||||
// TODO: this really should be strongly typed
|
|
||||||
Command::Focus(target.into())
|
|
||||||
}
|
|
||||||
"seek" => {
|
|
||||||
if args.is_empty() {
|
|
||||||
return Err(InsufficientArgs {
|
|
||||||
cmd: command.into(),
|
cmd: command.into(),
|
||||||
hint: Some("a duration".into()),
|
hint: Some("queue|search|library".into()),
|
||||||
});
|
})?;
|
||||||
|
// TODO: this really should be strongly typed
|
||||||
|
Command::Focus(target.into())
|
||||||
}
|
}
|
||||||
let arg = args.join(" ");
|
"seek" => {
|
||||||
let first_char = arg.chars().next();
|
if args.is_empty() {
|
||||||
let duration_raw = match first_char {
|
return Err(InsufficientArgs {
|
||||||
Some('+' | '-') => {
|
cmd: command.into(),
|
||||||
arg.chars().skip(1).collect::<String>().trim().into()
|
hint: Some("a duration".into()),
|
||||||
// `trim` is necessary here, otherwise `+1000` -> 1 second, but `+ 1000` -> 1000 seconds
|
});
|
||||||
// this behaviour is inconsistent and could cause confusion
|
|
||||||
}
|
}
|
||||||
_ => arg,
|
let arg = args.join(" ");
|
||||||
};
|
let first_char = arg.chars().next();
|
||||||
let unsigned_millis = match duration_raw.parse() {
|
let duration_raw = match first_char {
|
||||||
// accept raw milliseconds
|
Some('+' | '-') => {
|
||||||
Ok(millis) => millis,
|
arg.chars().skip(1).collect::<String>().trim().into()
|
||||||
Err(_) => parse_duration::parse(&duration_raw) // accept fancy duration
|
// `trim` is necessary here, otherwise `+1000` -> 1 second, but `+ 1000` -> 1000 seconds
|
||||||
.map_err(|err| ArgParseError {
|
// this behaviour is inconsistent and could cause confusion
|
||||||
arg: duration_raw.clone(),
|
}
|
||||||
err: err.to_string(),
|
_ => arg,
|
||||||
})
|
};
|
||||||
.and_then(|dur| {
|
let unsigned_millis = match duration_raw.parse() {
|
||||||
dur.as_millis().try_into().map_err(|_| ArgParseError {
|
// accept raw milliseconds
|
||||||
|
Ok(millis) => millis,
|
||||||
|
Err(_) => parse_duration::parse(&duration_raw) // accept fancy duration
|
||||||
|
.map_err(|err| ArgParseError {
|
||||||
arg: duration_raw.clone(),
|
arg: duration_raw.clone(),
|
||||||
err: "Duration value too large".into(),
|
err: err.to_string(),
|
||||||
})
|
})
|
||||||
})?,
|
.and_then(|dur| {
|
||||||
};
|
dur.as_millis().try_into().map_err(|_| ArgParseError {
|
||||||
let seek_direction = match first_char {
|
arg: duration_raw.clone(),
|
||||||
// handle i32::MAX < unsigned_millis < u32::MAX gracefully
|
err: "Duration value too large".into(),
|
||||||
Some('+') => i32::try_from(unsigned_millis).map(SeekDirection::Relative),
|
})
|
||||||
Some('-') => i32::try_from(unsigned_millis)
|
})?,
|
||||||
.map(|millis| SeekDirection::Relative(-millis)),
|
};
|
||||||
_ => Ok(SeekDirection::Absolute(unsigned_millis)),
|
let seek_direction = match first_char {
|
||||||
|
// handle i32::MAX < unsigned_millis < u32::MAX gracefully
|
||||||
|
Some('+') => i32::try_from(unsigned_millis).map(SeekDirection::Relative),
|
||||||
|
Some('-') => i32::try_from(unsigned_millis)
|
||||||
|
.map(|millis| SeekDirection::Relative(-millis)),
|
||||||
|
_ => Ok(SeekDirection::Absolute(unsigned_millis)),
|
||||||
|
}
|
||||||
|
.map_err(|_| ArgParseError {
|
||||||
|
arg: duration_raw,
|
||||||
|
err: "Duration value too large".into(),
|
||||||
|
})?;
|
||||||
|
Command::Seek(seek_direction)
|
||||||
}
|
}
|
||||||
.map_err(|_| ArgParseError {
|
"volup" => {
|
||||||
arg: duration_raw,
|
let amount = match args.get(0) {
|
||||||
err: "Duration value too large".into(),
|
Some(&amount_raw) => {
|
||||||
})?;
|
amount_raw.parse::<u16>().map_err(|err| ArgParseError {
|
||||||
Command::Seek(seek_direction)
|
arg: amount_raw.into(),
|
||||||
}
|
err: err.to_string(),
|
||||||
"volup" => {
|
})?
|
||||||
let amount = match args.get(0) {
|
}
|
||||||
Some(&amount_raw) => {
|
None => 1,
|
||||||
amount_raw.parse::<u16>().map_err(|err| ArgParseError {
|
};
|
||||||
arg: amount_raw.into(),
|
Command::VolumeUp(amount)
|
||||||
err: err.to_string(),
|
}
|
||||||
})?
|
"voldown" => {
|
||||||
}
|
let amount = match args.get(0) {
|
||||||
None => 1,
|
Some(&amount_raw) => {
|
||||||
};
|
amount_raw.parse::<u16>().map_err(|err| ArgParseError {
|
||||||
Command::VolumeUp(amount)
|
arg: amount_raw.into(),
|
||||||
}
|
err: err.to_string(),
|
||||||
"voldown" => {
|
})?
|
||||||
let amount = match args.get(0) {
|
}
|
||||||
Some(&amount_raw) => {
|
None => 1,
|
||||||
amount_raw.parse::<u16>().map_err(|err| ArgParseError {
|
};
|
||||||
arg: amount_raw.into(),
|
Command::VolumeDown(amount)
|
||||||
err: err.to_string(),
|
}
|
||||||
})?
|
"repeat" => {
|
||||||
}
|
let mode = match args.get(0).cloned() {
|
||||||
None => 1,
|
Some("list" | "playlist" | "queue") => {
|
||||||
};
|
Ok(Some(RepeatSetting::RepeatPlaylist))
|
||||||
Command::VolumeDown(amount)
|
}
|
||||||
}
|
Some("track" | "once" | "single") => Ok(Some(RepeatSetting::RepeatTrack)),
|
||||||
"repeat" => {
|
Some("none" | "off") => Ok(Some(RepeatSetting::None)),
|
||||||
let mode = match args.get(0).cloned() {
|
Some(arg) => Err(BadEnumArg {
|
||||||
Some("list" | "playlist" | "queue") => Ok(Some(RepeatSetting::RepeatPlaylist)),
|
arg: arg.into(),
|
||||||
Some("track" | "once" | "single") => Ok(Some(RepeatSetting::RepeatTrack)),
|
|
||||||
Some("none" | "off") => Ok(Some(RepeatSetting::None)),
|
|
||||||
Some(arg) => Err(BadEnumArg {
|
|
||||||
arg: arg.into(),
|
|
||||||
accept: vec![
|
|
||||||
"**omit**".into(),
|
|
||||||
"list".into(),
|
|
||||||
"playlist".into(),
|
|
||||||
"queue".into(),
|
|
||||||
"track".into(),
|
|
||||||
"once".into(),
|
|
||||||
"single".into(),
|
|
||||||
"none".into(),
|
|
||||||
"off".into(),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
None => Ok(None),
|
|
||||||
}?;
|
|
||||||
Command::Repeat(mode)
|
|
||||||
}
|
|
||||||
"shuffle" => {
|
|
||||||
let switch = match args.get(0).cloned() {
|
|
||||||
Some("on") => Ok(Some(true)),
|
|
||||||
Some("off") => Ok(Some(false)),
|
|
||||||
Some(arg) => Err(BadEnumArg {
|
|
||||||
arg: arg.into(),
|
|
||||||
accept: vec!["**omit**".into(), "on".into(), "off".into()],
|
|
||||||
}),
|
|
||||||
None => Ok(None),
|
|
||||||
}?;
|
|
||||||
Command::Shuffle(switch)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "share_clipboard")]
|
|
||||||
"share" => {
|
|
||||||
let &target_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
|
||||||
cmd: command.into(),
|
|
||||||
hint: Some("selected|current".into()),
|
|
||||||
})?;
|
|
||||||
let target_mode = match target_mode_raw {
|
|
||||||
"selected" => Ok(TargetMode::Selected),
|
|
||||||
"current" => Ok(TargetMode::Current),
|
|
||||||
_ => Err(BadEnumArg {
|
|
||||||
arg: target_mode_raw.into(),
|
|
||||||
accept: vec!["selected".into(), "current".into()],
|
|
||||||
}),
|
|
||||||
}?;
|
|
||||||
Command::Share(target_mode)
|
|
||||||
}
|
|
||||||
"back" => Command::Back,
|
|
||||||
"open" => {
|
|
||||||
let &target_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
|
||||||
cmd: command.into(),
|
|
||||||
hint: Some("selected|current".into()),
|
|
||||||
})?;
|
|
||||||
let target_mode = match target_mode_raw {
|
|
||||||
"selected" => Ok(TargetMode::Selected),
|
|
||||||
"current" => Ok(TargetMode::Current),
|
|
||||||
_ => Err(BadEnumArg {
|
|
||||||
arg: target_mode_raw.into(),
|
|
||||||
accept: vec!["selected".into(), "current".into()],
|
|
||||||
}),
|
|
||||||
}?;
|
|
||||||
Command::Open(target_mode)
|
|
||||||
}
|
|
||||||
"goto" => {
|
|
||||||
let &goto_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
|
||||||
cmd: command.into(),
|
|
||||||
hint: Some("album|artist".into()),
|
|
||||||
})?;
|
|
||||||
let goto_mode = match goto_mode_raw {
|
|
||||||
"album" => Ok(GotoMode::Album),
|
|
||||||
"artist" => Ok(GotoMode::Artist),
|
|
||||||
_ => Err(BadEnumArg {
|
|
||||||
arg: goto_mode_raw.into(),
|
|
||||||
accept: vec!["album".into(), "artist".into()],
|
|
||||||
}),
|
|
||||||
}?;
|
|
||||||
Command::Goto(goto_mode)
|
|
||||||
}
|
|
||||||
"move" => {
|
|
||||||
let &move_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
|
||||||
cmd: command.into(),
|
|
||||||
hint: Some("a direction".into()),
|
|
||||||
})?;
|
|
||||||
let move_mode = {
|
|
||||||
use MoveMode::*;
|
|
||||||
match move_mode_raw {
|
|
||||||
"playing" => Ok(Playing),
|
|
||||||
"top" | "up" => Ok(Up),
|
|
||||||
"bottom" | "down" => Ok(Down),
|
|
||||||
"leftmost" | "left" => Ok(Left),
|
|
||||||
"rightmost" | "right" => Ok(Right),
|
|
||||||
_ => Err(BadEnumArg {
|
|
||||||
arg: move_mode_raw.into(),
|
|
||||||
accept: vec![
|
accept: vec![
|
||||||
"playing".into(),
|
"**omit**".into(),
|
||||||
"top".into(),
|
"list".into(),
|
||||||
"bottom".into(),
|
"playlist".into(),
|
||||||
"leftmost".into(),
|
"queue".into(),
|
||||||
"rightmost".into(),
|
"track".into(),
|
||||||
"up".into(),
|
"once".into(),
|
||||||
"down".into(),
|
"single".into(),
|
||||||
"left".into(),
|
"none".into(),
|
||||||
"right".into(),
|
"off".into(),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
}?
|
None => Ok(None),
|
||||||
};
|
}?;
|
||||||
let move_amount = match move_mode_raw {
|
Command::Repeat(mode)
|
||||||
"playing" => Ok(MoveAmount::default()),
|
}
|
||||||
"top" | "bottom" | "leftmost" | "rightmost" => Ok(MoveAmount::Extreme),
|
"shuffle" => {
|
||||||
"up" | "down" | "left" | "right" => {
|
let switch = match args.get(0).cloned() {
|
||||||
let amount = match args.get(1) {
|
Some("on") => Ok(Some(true)),
|
||||||
Some(&amount_raw) => amount_raw
|
Some("off") => Ok(Some(false)),
|
||||||
.parse::<i32>()
|
Some(arg) => Err(BadEnumArg {
|
||||||
.map(MoveAmount::Integer)
|
arg: arg.into(),
|
||||||
.map_err(|err| ArgParseError {
|
accept: vec!["**omit**".into(), "on".into(), "off".into()],
|
||||||
|
}),
|
||||||
|
None => Ok(None),
|
||||||
|
}?;
|
||||||
|
Command::Shuffle(switch)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "share_clipboard")]
|
||||||
|
"share" => {
|
||||||
|
let &target_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
||||||
|
cmd: command.into(),
|
||||||
|
hint: Some("selected|current".into()),
|
||||||
|
})?;
|
||||||
|
let target_mode = match target_mode_raw {
|
||||||
|
"selected" => Ok(TargetMode::Selected),
|
||||||
|
"current" => Ok(TargetMode::Current),
|
||||||
|
_ => Err(BadEnumArg {
|
||||||
|
arg: target_mode_raw.into(),
|
||||||
|
accept: vec!["selected".into(), "current".into()],
|
||||||
|
}),
|
||||||
|
}?;
|
||||||
|
Command::Share(target_mode)
|
||||||
|
}
|
||||||
|
"back" => Command::Back,
|
||||||
|
"open" => {
|
||||||
|
let &target_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
||||||
|
cmd: command.into(),
|
||||||
|
hint: Some("selected|current".into()),
|
||||||
|
})?;
|
||||||
|
let target_mode = match target_mode_raw {
|
||||||
|
"selected" => Ok(TargetMode::Selected),
|
||||||
|
"current" => Ok(TargetMode::Current),
|
||||||
|
_ => Err(BadEnumArg {
|
||||||
|
arg: target_mode_raw.into(),
|
||||||
|
accept: vec!["selected".into(), "current".into()],
|
||||||
|
}),
|
||||||
|
}?;
|
||||||
|
Command::Open(target_mode)
|
||||||
|
}
|
||||||
|
"goto" => {
|
||||||
|
let &goto_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
||||||
|
cmd: command.into(),
|
||||||
|
hint: Some("album|artist".into()),
|
||||||
|
})?;
|
||||||
|
let goto_mode = match goto_mode_raw {
|
||||||
|
"album" => Ok(GotoMode::Album),
|
||||||
|
"artist" => Ok(GotoMode::Artist),
|
||||||
|
_ => Err(BadEnumArg {
|
||||||
|
arg: goto_mode_raw.into(),
|
||||||
|
accept: vec!["album".into(), "artist".into()],
|
||||||
|
}),
|
||||||
|
}?;
|
||||||
|
Command::Goto(goto_mode)
|
||||||
|
}
|
||||||
|
"move" => {
|
||||||
|
let &move_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
||||||
|
cmd: command.into(),
|
||||||
|
hint: Some("a direction".into()),
|
||||||
|
})?;
|
||||||
|
let move_mode = {
|
||||||
|
use MoveMode::*;
|
||||||
|
match move_mode_raw {
|
||||||
|
"playing" => Ok(Playing),
|
||||||
|
"top" | "up" => Ok(Up),
|
||||||
|
"bottom" | "down" => Ok(Down),
|
||||||
|
"leftmost" | "left" => Ok(Left),
|
||||||
|
"rightmost" | "right" => Ok(Right),
|
||||||
|
_ => Err(BadEnumArg {
|
||||||
|
arg: move_mode_raw.into(),
|
||||||
|
accept: vec![
|
||||||
|
"playing".into(),
|
||||||
|
"top".into(),
|
||||||
|
"bottom".into(),
|
||||||
|
"leftmost".into(),
|
||||||
|
"rightmost".into(),
|
||||||
|
"up".into(),
|
||||||
|
"down".into(),
|
||||||
|
"left".into(),
|
||||||
|
"right".into(),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}?
|
||||||
|
};
|
||||||
|
let move_amount = match move_mode_raw {
|
||||||
|
"playing" => Ok(MoveAmount::default()),
|
||||||
|
"top" | "bottom" | "leftmost" | "rightmost" => Ok(MoveAmount::Extreme),
|
||||||
|
"up" | "down" | "left" | "right" => {
|
||||||
|
let amount = match args.get(1) {
|
||||||
|
Some(&amount_raw) => amount_raw
|
||||||
|
.parse::<i32>()
|
||||||
|
.map(MoveAmount::Integer)
|
||||||
|
.map_err(|err| ArgParseError {
|
||||||
|
arg: amount_raw.into(),
|
||||||
|
err: err.to_string(),
|
||||||
|
})?,
|
||||||
|
None => MoveAmount::default(),
|
||||||
|
};
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
_ => unreachable!(), // already guarded when determining MoveMode
|
||||||
|
}?;
|
||||||
|
Command::Move(move_mode, move_amount)
|
||||||
|
}
|
||||||
|
"shift" => {
|
||||||
|
let &shift_dir_raw = args.get(0).ok_or(InsufficientArgs {
|
||||||
|
cmd: command.into(),
|
||||||
|
hint: Some("up|down".into()),
|
||||||
|
})?;
|
||||||
|
let shift_dir = match shift_dir_raw {
|
||||||
|
"up" => Ok(ShiftMode::Up),
|
||||||
|
"down" => Ok(ShiftMode::Down),
|
||||||
|
_ => Err(BadEnumArg {
|
||||||
|
arg: shift_dir_raw.into(),
|
||||||
|
accept: vec!["up".into(), "down".into()],
|
||||||
|
}),
|
||||||
|
}?;
|
||||||
|
let amount = match args.get(1) {
|
||||||
|
Some(&amount_raw) => {
|
||||||
|
let amount =
|
||||||
|
amount_raw.parse::<i32>().map_err(|err| ArgParseError {
|
||||||
arg: amount_raw.into(),
|
arg: amount_raw.into(),
|
||||||
err: err.to_string(),
|
err: err.to_string(),
|
||||||
})?,
|
})?;
|
||||||
None => MoveAmount::default(),
|
Some(amount)
|
||||||
};
|
}
|
||||||
Ok(amount)
|
None => None,
|
||||||
}
|
};
|
||||||
_ => unreachable!(), // already guarded when determining MoveMode
|
Command::Shift(shift_dir, amount)
|
||||||
}?;
|
}
|
||||||
Command::Move(move_mode, move_amount)
|
"search" => Command::Search(args.join(" ")),
|
||||||
}
|
"jump" => Command::Jump(JumpMode::Query(args.join(" "))),
|
||||||
"shift" => {
|
"jumpnext" => Command::Jump(JumpMode::Next),
|
||||||
let &shift_dir_raw = args.get(0).ok_or(InsufficientArgs {
|
"jumpprevious" => Command::Jump(JumpMode::Previous),
|
||||||
cmd: command.into(),
|
"help" => Command::Help,
|
||||||
hint: Some("up|down".into()),
|
"reload" => Command::ReloadConfig,
|
||||||
})?;
|
"noop" => Command::Noop,
|
||||||
let shift_dir = match shift_dir_raw {
|
"insert" => {
|
||||||
"up" => Ok(ShiftMode::Up),
|
let insert_source = match args.get(0).cloned() {
|
||||||
"down" => Ok(ShiftMode::Down),
|
|
||||||
_ => Err(BadEnumArg {
|
|
||||||
arg: shift_dir_raw.into(),
|
|
||||||
accept: vec!["up".into(), "down".into()],
|
|
||||||
}),
|
|
||||||
}?;
|
|
||||||
let amount = match args.get(1) {
|
|
||||||
Some(&amount_raw) => {
|
|
||||||
let amount = amount_raw.parse::<i32>().map_err(|err| ArgParseError {
|
|
||||||
arg: amount_raw.into(),
|
|
||||||
err: err.to_string(),
|
|
||||||
})?;
|
|
||||||
Some(amount)
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
Command::Shift(shift_dir, amount)
|
|
||||||
}
|
|
||||||
"search" => Command::Search(args.join(" ")),
|
|
||||||
"jump" => Command::Jump(JumpMode::Query(args.join(" "))),
|
|
||||||
"jumpnext" => Command::Jump(JumpMode::Next),
|
|
||||||
"jumpprevious" => Command::Jump(JumpMode::Previous),
|
|
||||||
"help" => Command::Help,
|
|
||||||
"reload" => Command::ReloadConfig,
|
|
||||||
"noop" => Command::Noop,
|
|
||||||
"insert" => {
|
|
||||||
let insert_source =
|
|
||||||
match args.get(0).cloned() {
|
|
||||||
#[cfg(feature = "share_clipboard")]
|
#[cfg(feature = "share_clipboard")]
|
||||||
Some("") | None => Ok(InsertSource::Clipboard),
|
Some("") | None => Ok(InsertSource::Clipboard),
|
||||||
// if clipboard feature is disabled and args is empty
|
// if clipboard feature is disabled and args is empty
|
||||||
@@ -648,85 +649,86 @@ pub fn parse(input: &str) -> Result<Vec<Command>, CommandParseError> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
}?;
|
}?;
|
||||||
Command::Insert(insert_source)
|
Command::Insert(insert_source)
|
||||||
}
|
}
|
||||||
"newplaylist" => {
|
"newplaylist" => {
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
Ok(Command::NewPlaylist(args.join(" ")))
|
Ok(Command::NewPlaylist(args.join(" ")))
|
||||||
} else {
|
} else {
|
||||||
Err(InsufficientArgs {
|
Err(InsufficientArgs {
|
||||||
|
cmd: command.into(),
|
||||||
|
hint: Some("a name".into()),
|
||||||
|
})
|
||||||
|
}?
|
||||||
|
}
|
||||||
|
"sort" => {
|
||||||
|
let &key_raw = args.get(0).ok_or(InsufficientArgs {
|
||||||
cmd: command.into(),
|
cmd: command.into(),
|
||||||
hint: Some("a name".into()),
|
hint: Some("a sort key".into()),
|
||||||
})
|
})?;
|
||||||
}?
|
let key = match key_raw {
|
||||||
}
|
"title" => Ok(SortKey::Title),
|
||||||
"sort" => {
|
"duration" => Ok(SortKey::Duration),
|
||||||
let &key_raw = args.get(0).ok_or(InsufficientArgs {
|
"album" => Ok(SortKey::Album),
|
||||||
cmd: command.into(),
|
"added" => Ok(SortKey::Added),
|
||||||
hint: Some("a sort key".into()),
|
"artist" => Ok(SortKey::Artist),
|
||||||
})?;
|
|
||||||
let key = match key_raw {
|
|
||||||
"title" => Ok(SortKey::Title),
|
|
||||||
"duration" => Ok(SortKey::Duration),
|
|
||||||
"album" => Ok(SortKey::Album),
|
|
||||||
"added" => Ok(SortKey::Added),
|
|
||||||
"artist" => Ok(SortKey::Artist),
|
|
||||||
_ => Err(BadEnumArg {
|
|
||||||
arg: key_raw.into(),
|
|
||||||
accept: vec![
|
|
||||||
"title".into(),
|
|
||||||
"duration".into(),
|
|
||||||
"album".into(),
|
|
||||||
"added".into(),
|
|
||||||
"artist".into(),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}?;
|
|
||||||
let direction = match args.get(1) {
|
|
||||||
Some(&direction_raw) => match direction_raw {
|
|
||||||
"a" | "asc" | "ascending" => Ok(SortDirection::Ascending),
|
|
||||||
"d" | "desc" | "descending" => Ok(SortDirection::Descending),
|
|
||||||
_ => Err(BadEnumArg {
|
_ => Err(BadEnumArg {
|
||||||
arg: direction_raw.into(),
|
arg: key_raw.into(),
|
||||||
accept: vec![
|
accept: vec![
|
||||||
"a".into(),
|
"title".into(),
|
||||||
"asc".into(),
|
"duration".into(),
|
||||||
"ascending".into(),
|
"album".into(),
|
||||||
"d".into(),
|
"added".into(),
|
||||||
"desc".into(),
|
"artist".into(),
|
||||||
"descending".into(),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
}?;
|
||||||
None => Ok(SortDirection::Ascending),
|
let direction = match args.get(1) {
|
||||||
}?;
|
Some(&direction_raw) => match direction_raw {
|
||||||
Command::Sort(key, direction)
|
"a" | "asc" | "ascending" => Ok(SortDirection::Ascending),
|
||||||
}
|
"d" | "desc" | "descending" => Ok(SortDirection::Descending),
|
||||||
"logout" => Command::Logout,
|
_ => Err(BadEnumArg {
|
||||||
"similar" => {
|
arg: direction_raw.into(),
|
||||||
let &target_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
accept: vec![
|
||||||
cmd: command.into(),
|
"a".into(),
|
||||||
hint: Some("selected|current".into()),
|
"asc".into(),
|
||||||
})?;
|
"ascending".into(),
|
||||||
let target_mode = match target_mode_raw {
|
"d".into(),
|
||||||
"selected" => Ok(TargetMode::Selected),
|
"desc".into(),
|
||||||
"current" => Ok(TargetMode::Current),
|
"descending".into(),
|
||||||
_ => Err(BadEnumArg {
|
],
|
||||||
arg: target_mode_raw.into(),
|
}),
|
||||||
accept: vec!["selected".into(), "current".into()],
|
},
|
||||||
}),
|
None => Ok(SortDirection::Ascending),
|
||||||
}?;
|
}?;
|
||||||
Command::ShowRecommendations(target_mode)
|
Command::Sort(key, direction)
|
||||||
}
|
}
|
||||||
"redraw" => Command::Redraw,
|
"logout" => Command::Logout,
|
||||||
"exec" => Command::Execute(args.join(" ")),
|
"similar" => {
|
||||||
_ => {
|
let &target_mode_raw = args.get(0).ok_or(InsufficientArgs {
|
||||||
return Err(NoSuchCommand {
|
cmd: command.into(),
|
||||||
cmd: command.into(),
|
hint: Some("selected|current".into()),
|
||||||
})
|
})?;
|
||||||
} // I'm surprised this compiles lol
|
let target_mode = match target_mode_raw {
|
||||||
|
"selected" => Ok(TargetMode::Selected),
|
||||||
|
"current" => Ok(TargetMode::Current),
|
||||||
|
_ => Err(BadEnumArg {
|
||||||
|
arg: target_mode_raw.into(),
|
||||||
|
accept: vec!["selected".into(), "current".into()],
|
||||||
|
}),
|
||||||
|
}?;
|
||||||
|
Command::ShowRecommendations(target_mode)
|
||||||
|
}
|
||||||
|
"redraw" => Command::Redraw,
|
||||||
|
"exec" => Command::Execute(args.join(" ")),
|
||||||
|
_ => {
|
||||||
|
return Err(NoSuchCommand {
|
||||||
|
cmd: command.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
commands.push(command);
|
||||||
};
|
};
|
||||||
commands.push(command);
|
|
||||||
}
|
}
|
||||||
Ok(commands)
|
Ok(commands)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user