1 use crate::app::mode::Mode; 2 use crate::app::prompt::OutputType; 3 use crate::app::selection::Selection; 4 use crate::app::style::Style; 5 use crate::gpg::key::KeyType; 6 use crate::widget::row::ScrollDirection; 7 use std::fmt::{Display, Formatter, Result as FmtResult}; 8 use std::str::FromStr; 9 10 /// Command to run on rendering process. 11 /// 12 /// It specifies the main operation to perform on [`App`]. 13 /// 14 /// [`App`]: crate::app::launcher::App 15 #[derive(Clone, Debug, PartialEq)] 16 pub enum Command { 17 /// Confirm the execution of a command. 18 Confirm(Box<Command>), 19 /// Show help. 20 ShowHelp, 21 /// Change application style. 22 ChangeStyle(Style), 23 /// Show application output. 24 ShowOutput(OutputType, String), 25 /// Show popup for options menu. 26 ShowOptions, 27 /// List the public/secret keys. 28 ListKeys(KeyType), 29 /// Import public/secret keys from files or a keyserver. 30 ImportKeys(Vec<String>, bool), 31 /// Import public/secret keys from clipboard. 32 ImportClipboard, 33 /// Export the public/secret keys. 34 ExportKeys(KeyType, Vec<String>, bool), 35 /// Delete the public/secret key. 36 DeleteKey(KeyType, String), 37 /// Send the key to the default keyserver. 38 SendKey(String), 39 /// Edit a key. 40 EditKey(String), 41 /// Sign a key. 42 SignKey(String), 43 /// Generate a new key pair. 44 GenerateKey, 45 /// Refresh the keyring. 46 RefreshKeys, 47 /// Copy a property to clipboard. 48 Copy(Selection), 49 /// Toggle the detail level. 50 ToggleDetail(bool), 51 /// Toggle the table size. 52 ToggleTableSize, 53 /// Scroll the currrent widget. 54 Scroll(ScrollDirection, bool), 55 /// Set the value of an option. 56 Set(String, String), 57 /// Get the value of an option. 58 Get(String), 59 /// Switch the application mode. 60 SwitchMode(Mode), 61 /// Paste the clipboard contents. 62 Paste, 63 /// Enable command input. 64 EnableInput, 65 /// Search for a value. 66 Search(Option<String>), 67 /// Select the next tab. 68 NextTab, 69 /// Select the previous tab. 70 PreviousTab, 71 /// Refresh the application. 72 Refresh, 73 /// Quit the application. 74 Quit, 75 /// Do nothing. 76 None, 77 } 78 79 impl Display for Command { fmt(&self, f: &mut Formatter<'_>) -> FmtResult80 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 81 write!( 82 f, 83 "{}", 84 match self { 85 Command::None => String::from("close menu"), 86 Command::Refresh => String::from("refresh application"), 87 Command::RefreshKeys => String::from("refresh the keyring"), 88 Command::ShowHelp => String::from("show help"), 89 Command::ChangeStyle(style) => { 90 match style { 91 Style::Plain => String::from("disable colors"), 92 Style::Colored => String::from("enable colors"), 93 } 94 } 95 Command::ListKeys(key_type) => { 96 format!( 97 "list {} keys", 98 format!("{:?}", key_type).to_lowercase() 99 ) 100 } 101 Command::ImportClipboard => { 102 String::from("import key(s) from clipboard") 103 } 104 Command::ExportKeys(key_type, patterns, ref export_subkeys) => { 105 if patterns.is_empty() { 106 format!("export all the keys ({})", key_type) 107 } else if *export_subkeys { 108 format!("export the selected subkeys ({})", key_type) 109 } else { 110 format!("export the selected key ({})", key_type) 111 } 112 } 113 Command::DeleteKey(key_type, _) => 114 format!("delete the selected key ({})", key_type), 115 Command::SendKey(_) => 116 String::from("send key to the keyserver"), 117 Command::EditKey(_) => String::from("edit the selected key"), 118 Command::SignKey(_) => String::from("sign the selected key"), 119 Command::GenerateKey => String::from("generate a new key pair"), 120 Command::Copy(copy_type) => 121 format!("copy {}", copy_type.to_string().to_lowercase()), 122 Command::Paste => String::from("paste from clipboard"), 123 Command::ToggleDetail(all) => format!( 124 "toggle detail ({})", 125 if *all { "all" } else { "selected" } 126 ), 127 Command::ToggleTableSize => String::from("toggle table size"), 128 Command::Set(option, ref value) => { 129 let action = 130 if value == "true" { "enable" } else { "disable" }; 131 match option.as_ref() { 132 "armor" => format!("{} armored output", action), 133 "signer" => String::from("set as the signing key"), 134 "margin" => String::from("toggle table margin"), 135 "prompt" => { 136 if value == ":import " { 137 String::from("import key(s) from a file") 138 } else if value == ":receive " { 139 String::from("receive key(s) from keyserver") 140 } else { 141 format!("set prompt text to {}", value) 142 } 143 } 144 _ => format!("set {} to {}", option, value), 145 } 146 } 147 Command::SwitchMode(mode) => format!( 148 "switch to {} mode", 149 format!("{:?}", mode).to_lowercase() 150 ), 151 Command::Quit => String::from("quit application"), 152 Command::Confirm(command) => (*command).to_string(), 153 _ => format!("{:?}", self), 154 } 155 ) 156 } 157 } 158 159 impl FromStr for Command { 160 type Err = (); from_str(s: &str) -> Result<Self, Self::Err>161 fn from_str(s: &str) -> Result<Self, Self::Err> { 162 let mut values = s 163 .replacen(':', "", 1) 164 .to_lowercase() 165 .split_whitespace() 166 .map(String::from) 167 .collect::<Vec<String>>(); 168 let command = values.first().cloned().unwrap_or_default(); 169 let args = values.drain(1..).collect::<Vec<String>>(); 170 match command.as_str() { 171 "confirm" => Ok(Command::Confirm(Box::new(if args.is_empty() { 172 Command::None 173 } else { 174 Command::from_str(&args.join(" "))? 175 }))), 176 "help" | "h" => Ok(Command::ShowHelp), 177 "style" => Ok(Command::ChangeStyle( 178 Style::from_str(&args.first().cloned().unwrap_or_default()) 179 .unwrap_or_default(), 180 )), 181 "output" | "out" => { 182 if !args.is_empty() { 183 Ok(Command::ShowOutput( 184 OutputType::from( 185 args.first().cloned().unwrap_or_default(), 186 ), 187 args[1..].join(" "), 188 )) 189 } else { 190 Err(()) 191 } 192 } 193 "options" | "opt" => Ok(Command::ShowOptions), 194 "list" | "ls" => Ok(Command::ListKeys(KeyType::from_str( 195 &args.first().cloned().unwrap_or_else(|| String::from("pub")), 196 )?)), 197 "import" | "receive" => Ok(Command::ImportKeys( 198 s.replacen(':', "", 1) 199 .split_whitespace() 200 .map(String::from) 201 .skip(1) 202 .collect(), 203 command.as_str() == "receive", 204 )), 205 "import-clipboard" => Ok(Command::ImportClipboard), 206 "export" | "exp" => { 207 let mut patterns = if !args.is_empty() { 208 args[1..].to_vec() 209 } else { 210 Vec::new() 211 }; 212 let export_subkeys = 213 patterns.last() == Some(&String::from("subkey")); 214 if export_subkeys { 215 patterns.truncate(patterns.len() - 1) 216 } 217 Ok(Command::ExportKeys( 218 KeyType::from_str( 219 &args 220 .first() 221 .cloned() 222 .unwrap_or_else(|| String::from("pub")), 223 )?, 224 patterns, 225 export_subkeys, 226 )) 227 } 228 "delete" | "del" => { 229 let key_id = args.get(1).cloned().unwrap_or_default(); 230 Ok(Command::DeleteKey( 231 KeyType::from_str( 232 &args 233 .get(0) 234 .cloned() 235 .unwrap_or_else(|| String::from("pub")), 236 )?, 237 if let Some(key) = key_id.strip_prefix("0x") { 238 format!("0x{}", key.to_string().to_uppercase()) 239 } else { 240 key_id 241 }, 242 )) 243 } 244 "send" => Ok(Command::SendKey(args.first().cloned().ok_or(())?)), 245 "edit" => Ok(Command::EditKey(args.first().cloned().ok_or(())?)), 246 "sign" => Ok(Command::SignKey(args.first().cloned().ok_or(())?)), 247 "generate" | "gen" => Ok(Command::GenerateKey), 248 "copy" | "c" => { 249 if let Some(arg) = args.first().cloned() { 250 Ok(Command::Copy( 251 Selection::from_str(&arg).map_err(|_| ())?, 252 )) 253 } else { 254 Ok(Command::SwitchMode(Mode::Copy)) 255 } 256 } 257 "toggle" | "t" => { 258 if args.first() == Some(&String::from("detail")) { 259 Ok(Command::ToggleDetail( 260 args.get(1) == Some(&String::from("all")), 261 )) 262 } else { 263 Ok(Command::ToggleTableSize) 264 } 265 } 266 "scroll" => { 267 let scroll_row = args.first() == Some(&String::from("row")); 268 Ok(Command::Scroll( 269 ScrollDirection::from_str(&if scroll_row { 270 args[1..].join(" ") 271 } else { 272 args.join(" ") 273 }) 274 .unwrap_or(ScrollDirection::Down(1)), 275 scroll_row, 276 )) 277 } 278 "set" | "s" => Ok(Command::Set( 279 args.get(0).cloned().unwrap_or_default(), 280 args.get(1).cloned().unwrap_or_default(), 281 )), 282 "get" | "g" => { 283 Ok(Command::Get(args.get(0).cloned().unwrap_or_default())) 284 } 285 "mode" | "m" => Ok(Command::SwitchMode(Mode::from_str( 286 &args.first().cloned().ok_or(())?, 287 )?)), 288 "normal" | "n" => Ok(Command::SwitchMode(Mode::Normal)), 289 "visual" | "v" => Ok(Command::SwitchMode(Mode::Visual)), 290 "paste" | "p" => Ok(Command::Paste), 291 "input" => Ok(Command::EnableInput), 292 "search" => Ok(Command::Search(args.first().cloned())), 293 "next" => Ok(Command::NextTab), 294 "previous" | "prev" => Ok(Command::PreviousTab), 295 "refresh" | "r" => { 296 if args.first() == Some(&String::from("keys")) { 297 Ok(Command::RefreshKeys) 298 } else { 299 Ok(Command::Refresh) 300 } 301 } 302 "quit" | "q" | "q!" => Ok(Command::Quit), 303 "none" => Ok(Command::None), 304 _ => Err(()), 305 } 306 } 307 } 308 309 #[cfg(test)] 310 mod tests { 311 use super::*; 312 use pretty_assertions::assert_eq; 313 #[test] test_app_command()314 fn test_app_command() { 315 assert_eq!( 316 Command::Confirm(Box::new(Command::None)), 317 Command::from_str(":confirm none").unwrap() 318 ); 319 assert_eq!(Command::ShowHelp, Command::from_str(":help").unwrap()); 320 assert_eq!( 321 Command::ShowOutput( 322 OutputType::Success, 323 String::from("operation successful"), 324 ), 325 Command::from_str(":out success operation successful").unwrap() 326 ); 327 assert_eq!( 328 Command::ChangeStyle(Style::Colored), 329 Command::from_str(":style colored").unwrap() 330 ); 331 assert_eq!( 332 Command::ChangeStyle(Style::Plain), 333 Command::from_str(":style plain").unwrap() 334 ); 335 assert_eq!( 336 Command::ShowOptions, 337 Command::from_str(":options").unwrap() 338 ); 339 for cmd in &[":list", ":list pub", ":ls", ":ls pub"] { 340 let command = Command::from_str(cmd).unwrap(); 341 assert_eq!(Command::ListKeys(KeyType::Public), command); 342 } 343 for cmd in &[":list sec", ":ls sec"] { 344 let command = Command::from_str(cmd).unwrap(); 345 assert_eq!(Command::ListKeys(KeyType::Secret), command); 346 } 347 assert_eq!( 348 Command::ImportKeys( 349 vec![ 350 String::from("Test1"), 351 String::from("Test2"), 352 String::from("tesT3") 353 ], 354 false 355 ), 356 Command::from_str(":import Test1 Test2 tesT3").unwrap() 357 ); 358 assert_eq!( 359 Command::ImportKeys(vec![String::from("Test"),], true), 360 Command::from_str(":receive Test").unwrap() 361 ); 362 assert_eq!( 363 Command::ImportClipboard, 364 Command::from_str(":import-clipboard").unwrap() 365 ); 366 for cmd in &[":export", ":export pub", ":exp", ":exp pub"] { 367 let command = Command::from_str(cmd).unwrap(); 368 assert_eq!( 369 Command::ExportKeys(KeyType::Public, Vec::new(), false), 370 command 371 ); 372 } 373 assert_eq!( 374 Command::ExportKeys( 375 KeyType::Public, 376 vec![String::from("test1"), String::from("test2")], 377 false 378 ), 379 Command::from_str(":export pub test1 test2").unwrap() 380 ); 381 assert_eq!( 382 Command::ExportKeys( 383 KeyType::Secret, 384 vec![String::from("test3"), String::from("test4")], 385 true 386 ), 387 Command::from_str(":export sec test3 test4 subkey").unwrap() 388 ); 389 for cmd in &[":export sec", ":exp sec"] { 390 let command = Command::from_str(cmd).unwrap(); 391 assert_eq!( 392 Command::ExportKeys(KeyType::Secret, Vec::new(), false), 393 command 394 ); 395 } 396 assert_eq!( 397 Command::ExportKeys( 398 KeyType::Secret, 399 vec![ 400 String::from("test1"), 401 String::from("test2"), 402 String::from("test3") 403 ], 404 false 405 ), 406 Command::from_str(":export sec test1 test2 test3").unwrap() 407 ); 408 for cmd in &[":delete pub xyz", ":del pub xyz"] { 409 let command = Command::from_str(cmd).unwrap(); 410 assert_eq!( 411 Command::DeleteKey(KeyType::Public, String::from("xyz")), 412 command 413 ); 414 } 415 assert_eq!( 416 Command::SendKey(String::from("test")), 417 Command::from_str(":send test").unwrap() 418 ); 419 assert_eq!( 420 Command::EditKey(String::from("test")), 421 Command::from_str(":edit test").unwrap() 422 ); 423 assert_eq!( 424 Command::SignKey(String::from("test")), 425 Command::from_str(":sign test").unwrap() 426 ); 427 assert_eq!( 428 Command::GenerateKey, 429 Command::from_str(":generate").unwrap() 430 ); 431 assert_eq!( 432 Command::RefreshKeys, 433 Command::from_str(":refresh keys").unwrap() 434 ); 435 for cmd in &[":toggle detail all", ":t detail all"] { 436 let command = Command::from_str(cmd).unwrap(); 437 assert_eq!(Command::ToggleDetail(true), command); 438 } 439 assert_eq!( 440 Command::ToggleTableSize, 441 Command::from_str(":toggle").unwrap() 442 ); 443 for cmd in &[":scroll up 1", ":scroll u 1"] { 444 let command = Command::from_str(cmd).unwrap(); 445 assert_eq!(Command::Scroll(ScrollDirection::Up(1), false), command); 446 } 447 for cmd in &[":set armor true", ":s armor true"] { 448 let command = Command::from_str(cmd).unwrap(); 449 assert_eq!( 450 Command::Set(String::from("armor"), String::from("true")), 451 command 452 ); 453 } 454 for cmd in &[":get armor", ":g armor"] { 455 let command = Command::from_str(cmd).unwrap(); 456 assert_eq!(Command::Get(String::from("armor")), command); 457 } 458 assert_eq!( 459 Command::Set(String::from("test"), String::from("_")), 460 Command::from_str(":set test _").unwrap() 461 ); 462 for cmd in &[":normal", ":n"] { 463 let command = Command::from_str(cmd).unwrap(); 464 assert_eq!(Command::SwitchMode(Mode::Normal), command); 465 } 466 for cmd in &[":visual", ":v"] { 467 let command = Command::from_str(cmd).unwrap(); 468 assert_eq!(Command::SwitchMode(Mode::Visual), command); 469 } 470 for cmd in &[":copy", ":c"] { 471 let command = Command::from_str(cmd).unwrap(); 472 assert_eq!(Command::SwitchMode(Mode::Copy), command); 473 } 474 for cmd in &[":paste", ":p"] { 475 let command = Command::from_str(cmd).unwrap(); 476 assert_eq!(Command::Paste, command); 477 } 478 assert_eq!( 479 Command::Search(Some(String::from("q"))), 480 Command::from_str(":search q").unwrap() 481 ); 482 assert_eq!(Command::EnableInput, Command::from_str(":input").unwrap()); 483 assert_eq!(Command::NextTab, Command::from_str(":next").unwrap()); 484 assert_eq!(Command::PreviousTab, Command::from_str(":prev").unwrap()); 485 assert_eq!(Command::Refresh, Command::from_str(":refresh").unwrap()); 486 for cmd in &[":quit", ":q", ":q!"] { 487 let command = Command::from_str(cmd).unwrap(); 488 assert_eq!(Command::Quit, command); 489 } 490 assert_eq!(Command::None, Command::from_str(":none").unwrap()); 491 assert!(Command::from_str("test").is_err()); 492 493 assert_eq!("close menu", Command::None.to_string()); 494 assert_eq!("show help", Command::ShowHelp.to_string()); 495 assert_eq!( 496 "disable colors", 497 Command::ChangeStyle(Style::Plain).to_string() 498 ); 499 assert_eq!( 500 "enable colors", 501 Command::ChangeStyle(Style::Colored).to_string() 502 ); 503 assert_eq!("refresh application", Command::Refresh.to_string()); 504 assert_eq!("refresh the keyring", Command::RefreshKeys.to_string()); 505 assert_eq!( 506 "list public keys", 507 Command::ListKeys(KeyType::Public).to_string() 508 ); 509 assert_eq!( 510 "export all the keys (sec)", 511 Command::ExportKeys(KeyType::Secret, Vec::new(), false).to_string() 512 ); 513 assert_eq!( 514 "export the selected subkeys (sec)", 515 Command::ExportKeys(KeyType::Secret, vec![String::new()], true) 516 .to_string() 517 ); 518 assert_eq!( 519 "export the selected key (pub)", 520 Command::ExportKeys(KeyType::Public, vec![String::new()], false) 521 .to_string() 522 ); 523 assert_eq!( 524 "delete the selected key (pub)", 525 Command::DeleteKey(KeyType::Public, String::new()).to_string() 526 ); 527 assert_eq!( 528 "send key to the keyserver", 529 Command::SendKey(String::new()).to_string() 530 ); 531 assert_eq!( 532 "edit the selected key", 533 Command::EditKey(String::new()).to_string() 534 ); 535 assert_eq!( 536 "sign the selected key", 537 Command::SignKey(String::new()).to_string() 538 ); 539 assert_eq!("generate a new key pair", Command::GenerateKey.to_string()); 540 assert_eq!( 541 "copy exported key", 542 Command::Copy(Selection::Key).to_string() 543 ); 544 assert_eq!("paste from clipboard", Command::Paste.to_string()); 545 assert_eq!( 546 "toggle detail (all)", 547 Command::ToggleDetail(true).to_string() 548 ); 549 assert_eq!( 550 "toggle detail (selected)", 551 Command::ToggleDetail(false).to_string() 552 ); 553 assert_eq!("toggle table size", Command::ToggleTableSize.to_string()); 554 assert_eq!( 555 "disable armored output", 556 Command::Set(String::from("armor"), String::from("false")) 557 .to_string() 558 ); 559 assert_eq!( 560 "set style to colored", 561 Command::Set(String::from("style"), String::from("colored")) 562 .to_string() 563 ); 564 assert_eq!( 565 "toggle table margin", 566 Command::Set(String::from("margin"), String::new()).to_string() 567 ); 568 assert_eq!( 569 "import key(s) from a file", 570 Command::Set(String::from("prompt"), String::from(":import ")) 571 .to_string() 572 ); 573 assert_eq!( 574 "import key(s) from clipboard", 575 Command::ImportClipboard.to_string() 576 ); 577 assert_eq!( 578 "receive key(s) from keyserver", 579 Command::Set(String::from("prompt"), String::from(":receive ")) 580 .to_string() 581 ); 582 assert_eq!( 583 "set prompt text to xyz", 584 Command::Set(String::from("prompt"), String::from("xyz")) 585 .to_string() 586 ); 587 assert_eq!( 588 "set x to y", 589 Command::Set(String::from("x"), String::from("y")).to_string() 590 ); 591 assert_eq!( 592 "switch to visual mode", 593 Command::SwitchMode(Mode::Visual).to_string() 594 ); 595 assert_eq!( 596 "refresh application", 597 Command::Confirm(Box::new(Command::Refresh)).to_string() 598 ); 599 assert_eq!("quit application", Command::Quit.to_string()); 600 assert_eq!("NextTab", Command::NextTab.to_string()); 601 } 602 } 603