shadow_crypt_shell/listing/
cli.rs1use std::path::PathBuf;
2
3use clap::{Parser, error::ErrorKind};
4
5use crate::errors::WorkflowError;
6
7#[derive(Debug, Clone, Default, Parser)]
9#[command(
10 name = "shadows",
11 about = "List shadow files in a directory",
12 version,
13 after_help = "By default the original filenames are decrypted (prompts for the \
14 password and runs the full key derivation for every file); files the \
15 password does not open are still listed by their obfuscated names. \
16 With --no-names, no password is needed and only the plaintext header \
17 metadata is shown."
18)]
19pub struct ListingCliArgs {
20 #[arg(value_name = "DIR")]
22 pub dir: Option<PathBuf>,
23
24 #[arg(long = "no-names", conflicts_with = "password_file")]
26 pub no_names: bool,
27
28 #[arg(long = "password-file", value_name = "FILE")]
30 pub password_file: Option<PathBuf>,
31
32 #[arg(long = "json")]
34 pub json: bool,
35}
36
37pub fn get_cli_args(args: Vec<String>) -> Result<ListingCliArgs, WorkflowError> {
39 ListingCliArgs::try_parse_from(args).map_err(|e| {
40 if e.kind() == ErrorKind::DisplayHelp || e.kind() == ErrorKind::DisplayVersion {
42 eprintln!("{}", e);
44 std::process::exit(0);
45 }
46 WorkflowError::UserInput(e.to_string())
47 })
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn test_defaults_to_names_and_no_dir() {
56 let args = get_cli_args(vec!["shadows".to_string()]).unwrap();
57 assert!(!args.no_names);
58 assert!(args.dir.is_none());
59 }
60
61 #[test]
62 fn test_parses_dir_and_no_names() {
63 let args = get_cli_args(vec![
64 "shadows".to_string(),
65 "--no-names".to_string(),
66 "/some/dir".to_string(),
67 ])
68 .unwrap();
69 assert!(args.no_names);
70 assert_eq!(args.dir, Some(PathBuf::from("/some/dir")));
71 }
72
73 #[test]
74 fn test_no_names_conflicts_with_password_file() {
75 let result = get_cli_args(vec![
76 "shadows".to_string(),
77 "--no-names".to_string(),
78 "--password-file".to_string(),
79 "pw.txt".to_string(),
80 ]);
81 assert!(result.is_err());
82 }
83}