1use std::{env, fmt::Display, fs::File, io::Read, path::PathBuf};
2
3use clap::{ArgAction, Parser, Subcommand};
4use eyre::Result;
5use toml::Table;
6
7use crate::pls_command::PlsCommand;
8use crate::vendors::Vendor;
9
10
11#[derive(Debug, Parser)]
12#[command(about, author, name = "please", version)]
13pub struct Params {
14 #[arg(short = 'x', long, global = true, action = ArgAction::SetTrue)]
16 pub skip_settings: bool,
17
18 #[arg(short, long, global = true)]
20 pub config: Option<String>,
21
22 #[arg(short, long, global = true, action = ArgAction::SetTrue)]
24 pub dry_run: bool,
25
26 #[arg(short, long, global = true, action = ArgAction::SetTrue)]
28 pub yes: bool,
29
30 #[cfg(not(target_os = "windows"))]
31 #[arg(short, long, global = true, action = ArgAction::SetTrue)]
33 pub su: bool,
34
35 #[arg(short, long, global = true)]
37 pub vendor: Option<Vendor>,
38
39 #[command(subcommand)]
40 pub cmd: Cmd,
41
42 pub default_vendor: Option<Vendor>,
43}
44
45#[derive(Clone, Debug, PartialEq, Subcommand)]
46pub enum Cmd {
47 #[command()]
49 Install {
50 #[arg(name = "PACKAGE")]
52 args: Vec<String>,
53 },
54
55 #[command()]
57 ReinstallAll,
58
59 #[command(alias = "uninstall")]
61 Remove {
62 #[arg(name = "PACKAGE")]
64 args: Vec<String>,
65 },
66
67 #[command()]
69 Upgrade {
70 #[arg(name = "PACKAGE")]
72 args: Vec<String>,
73 },
74
75 #[command()]
77 Search {
78 #[arg(name = "QUERY")]
80 args: String,
81
82 #[arg(short, long, action = ArgAction::SetTrue)]
84 paginate: bool,
85
86 #[arg(skip)]
87 pager: Option<String>,
88 },
89
90 #[command()]
92 Info {
93 #[arg(name = "PACKAGE")]
95 args: String,
96 },
97
98 #[command()]
100 Update,
101
102 #[command()]
104 List {
105 #[arg(short, long, action = ArgAction::SetTrue)]
107 paginate: bool,
108
109 #[arg(skip)]
110 pager: Option<String>,
111 },
112
113 #[command()]
115 ListVendors,
116}
117
118impl Params {
119 pub fn config(mut self) -> Self {
120 if self.skip_settings {
121 return self;
122 }
123
124 #[cfg(target_os = "windows")]
125 const XDG_CONFIG_HOME: &str = "APPDATA";
126 #[cfg(not(target_os = "windows"))]
127 const XDG_CONFIG_HOME: &str = "XDG_CONFIG_HOME";
128
129 #[cfg(target_os = "windows")]
130 const CONFIG_HOME: &str = "AppData";
131 #[cfg(not(target_os = "windows"))]
132 const CONFIG_HOME: &str = ".config";
133
134 let config = match &self.config {
135 Some(config) => PathBuf::from(config),
136 None => {
137 let config_home = match env::var(XDG_CONFIG_HOME) {
138 Ok(config_home) => PathBuf::from(config_home),
139 Err(_) => PathBuf::from(env!["HOME"]).join(CONFIG_HOME),
140 };
141 config_home.join("please.toml")
142 }
143 };
144 if config.exists() {
145 let _ = self.load(config);
146 }
147
148 self
149 }
150
151 pub fn actual_vendor(&self) -> Result<Vendor> {
152 match self.vendor.or(self.default_vendor) {
153 Some(vendor) => Ok(vendor),
154 None => Vendor::new(),
155 }
156 }
157
158 fn load(&mut self, config: PathBuf) -> Result<()> {
159 let mut file = File::open(config)?;
160 let mut content = String::new();
161 file.read_to_string(&mut content)?;
162 let mut defaults: Table = content.parse()?;
163 let cmd = self.cmd.to_string();
164 let bind = defaults.clone();
165 if let Some(value) = bind.get(cmd.as_str()).and_then(|value| value.as_table()) {
166 for (k, v) in value.iter() {
167 defaults.insert(k.to_string(), v.clone());
168 }
169 }
170
171 match &self.cmd {
172 Cmd::Search { args, .. } => {
173 if defaults.get("pager").is_some() {
174 self.cmd = Cmd::Search {
175 pager: defaults
176 .get("pager")
177 .and_then(|pager| pager.as_str())
178 .map(|pager| pager.to_owned())
179 .filter(|pager| !pager.is_empty())
180 .map(|pager| pager.replace("$args", args)),
181 paginate: true,
182 args: args.to_owned(),
183 }
184 }
185 }
186 Cmd::List { .. } => {
187 if defaults.get("pager").is_some() {
188 self.cmd = Cmd::List {
189 pager: defaults
190 .get("pager")
191 .and_then(|pager| pager.as_str())
192 .map(|pager| pager.to_owned())
193 .filter(|pager| !pager.is_empty()),
194 paginate: true,
195 }
196 }
197 }
198 _ => (),
199 }
200
201 if defaults.get("assume-yes").and_then(|yes| yes.as_bool()).unwrap_or_default() {
202 self.yes = true;
203 }
204 if defaults.get("su").and_then(|yes| yes.as_bool()).unwrap_or_default() {
205 self.su = true;
206 }
207 if self.vendor.is_none() {
208 if let Some(vendor) = defaults.get("vendor").and_then(|vendor| vendor.as_str()) {
209 let vendor: Vendor = vendor.try_into()?;
210 self.default_vendor = Some(vendor);
211 }
212 }
213
214 Ok(())
215 }
216}
217
218impl Cmd {
219 pub fn args(&self) -> Vec<String> {
220 match self {
221 Cmd::Install { args } => args.clone(),
222 Cmd::Remove { args } => args.clone(),
223 Cmd::Upgrade { args } => args.clone(),
224 Cmd::Search { args, .. } => vec![args.to_string()],
225 Cmd::Info { args } => vec![args.to_string()],
226 _ => Vec::new(),
227 }
228 }
229}
230
231impl Display for Cmd {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 match self {
234 Cmd::Install { .. } => write!(f, "install"),
235 Cmd::ReinstallAll => write!(f, "reinstall-all"),
236 Cmd::Remove { .. } => write!(f, "remove"),
237 Cmd::Upgrade { .. } => write!(f, "upgrade"),
238 Cmd::Search { .. } => write!(f, "search"),
239 Cmd::Info { .. } => write!(f, "info"),
240 Cmd::Update => write!(f, "update"),
241 Cmd::List { .. } => write!(f, "list"),
242 Cmd::ListVendors => write!(f, "list-vendors"),
243 }
244 }
245}
246
247impl From<&Cmd> for PlsCommand {
248 fn from(value: &Cmd) -> Self {
249 match value {
250 Cmd::Install {..} => PlsCommand::Install,
251 Cmd::ReinstallAll => PlsCommand::ReinstallAll,
252 Cmd::Remove {..} => PlsCommand::Remove,
253 Cmd::Upgrade {args} if args.is_empty() => PlsCommand::UpgradeAll,
254 Cmd::Upgrade {..} => PlsCommand::Upgrade,
255 Cmd::Search {..} => PlsCommand::Search,
256 Cmd::Info {..} => PlsCommand::Info,
257 Cmd::Update => PlsCommand::Update,
258 Cmd::List { .. } |
259 Cmd::ListVendors => PlsCommand::List,
260 }
261 }
262}