1use std::{ffi::OsString, iter::once, path::PathBuf};
2
3#[derive(Debug, Clone)]
4pub struct Config {
5 pub name: OsString,
6 pub pass: PathBuf,
7 pub dir: PathBuf,
8 pub find: PathBuf,
9 pub picker: PathBuf,
10 pub picker_args: Vec<OsString>,
11 pub typist: PathBuf,
12 pub typist_args: Vec<OsString>,
13}
14impl Default for Config {
15 fn default() -> Self {
16 Self {
17 name: "pass-fu".into(),
18 dir: "~/.password-store".into(),
19 pass: "/usr/bin/pass".into(),
20 find: "find".into(),
21 picker: "dmenu".into(),
22 picker_args: vec![],
23 typist: "xdotool".into(),
24 typist_args: ["type", "--clearmodifiers", "--file", "-"]
25 .into_iter()
26 .map(|a| a.into())
27 .collect(),
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
33pub struct Opt {
34 pub config: Config,
35 pub command: Command,
36}
37impl AsRef<Opt> for Opt {
38 fn as_ref(&self) -> &Opt {
39 self
40 }
41}
42impl Opt {
43 pub fn parse(
44 args: impl IntoIterator<Item = OsString>,
45 env: impl IntoIterator<Item = (OsString, OsString)>,
46 ) -> Result<Opt, impl std::fmt::Display> {
47 let env = env.into_iter();
48 let mut args = args.into_iter();
49 let mut config = Config::default();
50 config.name = args.next().unwrap_or(config.name);
51
52 let command = Command::parse(args);
53 for (key, val) in env {
54 match key.to_str() {
55 Some("PASSWORD_STORE_DIR") => config.dir = val.into(),
56 _ => {}
57 }
58 }
59 Result::<Opt, &str>::Ok(Self { config, command })
60 }
61}
62
63#[derive(Debug, Clone)]
64pub enum Command {
65 Show(ShowOpt),
66 Other(PassOpt),
67}
68impl Command {
69 fn parse(args: impl IntoIterator<Item = OsString>) -> Command {
70 let mut args = args.into_iter();
71 match args.next() {
75 None => Self::parse_show(args),
76 Some(arg) => match arg.to_str() {
77 None => Self::parse_other(arg, args),
78 Some(cmd) => match cmd {
79 "--help" | "--version" | "init" | "find" | "grep" | "insert" | "edit"
80 | "generate" | "rm" | "mv" | "cp" | "git" | "help" | "version" | "ls"
81 | "otp" => {
83 Self::parse_other(arg, args)
84 }
85 "show" => Self::parse_show(args),
86 _positional => Self::parse_show(once(arg).chain(args)),
87 },
88 },
89 }
90 }
91 fn parse_other(command: OsString, args: impl IntoIterator<Item = OsString>) -> Command {
92 Self::Other(PassOpt {
93 command,
94 rest: args.into_iter().collect(),
95 })
96 }
97 fn parse_show(args: impl IntoIterator<Item = OsString>) -> Command {
98 let mut args = args.into_iter();
99 let mut line = String::new();
100 let mut mode = ShowMode::default();
101 let mut path = None;
102 let mut rest = vec![];
103 let mut options_done = false;
104 while let Some(arg) = args.next() {
105 match arg.to_str().and_then(|a| {
106 a.split_once("=")
110 .or_else(|| Some((a, "")))
111 .filter(|(o, _)| o.starts_with("--"))
112 .or_else(|| a.len().checked_sub(2).map(|_| a.split_at(2)))
113 .filter(|(o, _)| o.starts_with("-"))
114 .filter(|_| !options_done)
115 }) {
116 Some(("-c", v)) | Some(("--clip", v)) => {
117 mode = ShowMode::Clip;
118 line = v.to_owned();
119 }
120 Some(("-q", v)) | Some(("--qrcode", v)) => {
121 mode = ShowMode::QrCode;
122 line = v.to_owned();
123 }
124 Some(("-t", v)) | Some(("--type", v)) => {
125 mode = ShowMode::Type;
126 line = v.to_owned();
127 }
128 Some(("-o", v)) | Some(("--output", v)) => {
129 mode = ShowMode::Output;
130 line = v.to_owned();
131 }
132 Some(("--", "")) => {
133 options_done = true;
134 rest.push(arg);
135 }
136 Some(_option) => rest.push(arg),
137 None => {
138 path = path.or(Some(PathBuf::from(arg)));
141 }
142 }
143 }
144 Command::Show(ShowOpt {
145 path,
146 line,
147 mode,
148 rest,
149 })
150 }
151}
152
153#[derive(Debug, Default, Clone)]
154pub struct ShowOpt {
155 pub path: Option<PathBuf>,
156 pub line: String,
157 pub mode: ShowMode,
158 pub rest: Vec<OsString>,
159}
160#[derive(Debug, Clone)]
161pub enum ShowMode {
162 Output,
163 Clip,
164 QrCode,
165 Type,
166}
167impl Default for ShowMode {
168 fn default() -> Self {
169 ShowMode::Output
170 }
171}
172#[derive(Debug, Default, Clone)]
173pub struct PassOpt {
174 pub command: OsString,
175 pub rest: Vec<OsString>,
176}