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 = 'n', 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(long, global = true, action = ArgAction::SetTrue)]
33 pub su: bool,
34
35 #[arg(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 #[command()]
119 Move {
120
121 #[command(subcommand)]
122 args: FromToken,
123 },
124
125 #[cfg(target_os = "linux")]
126 #[command()]
128 Enable {
129 #[arg(short, long, action = ArgAction::SetTrue)]
131 user: bool,
132
133 #[arg(name = "SERVICE")]
135 service: String,
136 },
137
138 #[cfg(target_os = "linux")]
139 #[command()]
141 Disable {
142 #[arg(short, long, action = ArgAction::SetTrue)]
144 user: bool,
145
146 #[arg(name = "SERVICE")]
148 service: String,
149 },
150
151 #[cfg(target_os = "linux")]
152 #[command()]
154 Start {
155 #[arg(short, long, action = ArgAction::SetTrue)]
157 user: bool,
158
159 #[arg(name = "SERVICE")]
161 service: String,
162 },
163
164 #[cfg(target_os = "linux")]
165 #[command()]
167 Stop {
168 #[arg(short, long, action = ArgAction::SetTrue)]
170 user: bool,
171
172 #[arg(name = "SERVICE")]
174 service: String,
175 },
176
177 #[cfg(target_os = "linux")]
178 #[command()]
180 Restart {
181 #[arg(short, long, action = ArgAction::SetTrue)]
183 user: bool,
184
185 #[arg(name = "SERVICE")]
187 service: String,
188 },
189
190 #[cfg(target_os = "linux")]
191 #[command()]
193 Status {
194 #[arg(short, long, action = ArgAction::SetTrue)]
196 user: bool,
197
198 #[arg(name = "SERVICE")]
200 service: String,
201 },
202
203 #[cfg(target_os = "linux")]
204 #[command()]
206 Reload {
207 #[arg(short, long, action = ArgAction::SetTrue)]
209 user: bool,
210 },
211}
212
213
214#[derive(Clone, Debug, PartialEq, Subcommand)]
215pub enum FromToken {
216
217 #[command()]
218 From {
219 #[arg(name = "ORIGIN")]
221 origin: Vendor,
222
223 #[command(subcommand)]
224 to: ToToken,
225 }
226}
227
228#[derive(Clone, Debug, PartialEq, Subcommand)]
229pub enum ToToken {
230
231 #[command()]
232 To {
233 #[arg(name = "DESTINATION")]
235 destination: Vendor,
236 }
237}
238
239impl Params {
240 pub fn config(mut self) -> Self {
241 if self.skip_settings {
242 return self;
243 }
244
245 let config = match &self.config {
246 Some(config) => PathBuf::from(config),
247 None => {
248 let config_home = match env::var(XDG_CONFIG_HOME) {
249 Ok(config_home) => PathBuf::from(config_home),
250 Err(_) => PathBuf::from(env!["HOME"]).join(CONFIG_HOME),
251 };
252 config_home.join("please.toml")
253 }
254 };
255 if config.exists() {
256 let _ = self.load(config);
257 }
258
259 self
260 }
261
262 pub fn actual_vendor(&self) -> Result<Vendor> {
263 match self.vendor.or(self.default_vendor) {
264 Some(vendor) => Ok(vendor),
265 None => Vendor::new(),
266 }
267 }
268
269 fn load(&mut self, config: PathBuf) -> Result<()> {
270 let mut file = File::open(config)?;
271 let mut content = String::new();
272 file.read_to_string(&mut content)?;
273 let mut defaults: Table = content.parse()?;
274 let cmd = self.cmd.to_string();
275 let bind = defaults.clone();
276 if let Some(value) = bind.get(cmd.as_str()).and_then(|value| value.as_table()) {
277 for (k, v) in value.iter() {
278 defaults.insert(k.to_string(), v.clone());
279 }
280 }
281
282 match &self.cmd {
283 Cmd::Search { args, .. } => {
284 if defaults.get("pager").is_some() {
285 self.cmd = Cmd::Search {
286 pager: defaults
287 .get("pager")
288 .and_then(|pager| pager.as_str())
289 .map(|pager| pager.to_owned())
290 .filter(|pager| !pager.is_empty())
291 .map(|pager| pager.replace("$args", args)),
292 paginate: true,
293 args: args.to_owned(),
294 }
295 }
296 }
297 Cmd::List { .. } => {
298 if defaults.get("pager").is_some() {
299 self.cmd = Cmd::List {
300 pager: defaults
301 .get("pager")
302 .and_then(|pager| pager.as_str())
303 .map(|pager| pager.to_owned())
304 .filter(|pager| !pager.is_empty()),
305 paginate: true,
306 }
307 }
308 }
309 _ => (),
310 }
311
312 if defaults.get("assume-yes").and_then(|yes| yes.as_bool()).unwrap_or_default() {
313 self.yes = true;
314 }
315 if defaults.get("su").and_then(|yes| yes.as_bool()).unwrap_or_default() {
316 self.su = true;
317 }
318 if self.vendor.is_none() {
319 if let Some(vendor) = defaults.get("vendor").and_then(|vendor| vendor.as_str()) {
320 let vendor: Vendor = vendor.try_into()?;
321 self.default_vendor = Some(vendor);
322 }
323 }
324
325 Ok(())
326 }
327}
328
329impl Cmd {
330 pub fn args(&self) -> Vec<String> {
331 match self {
332 Cmd::Install { args } => args.clone(),
333 Cmd::Remove { args } => args.clone(),
334 Cmd::Upgrade { args } => args.clone(),
335 Cmd::Search { args, .. } => vec![args.to_string()],
336 Cmd::Info { args } => vec![args.to_string()],
337 _ => Vec::new(),
338 }
339 }
340
341 #[cfg(target_os = "linux")]
342 pub fn use_su(&self) -> Option<bool> {
343 match self {
344 Cmd::Enable { user, .. } => Some(!*user),
345 Cmd::Disable { user, .. } => Some(!*user),
346 Cmd::Start { user, .. } => Some(!*user),
347 Cmd::Stop { user, .. } => Some(!*user),
348 Cmd::Restart { user, .. } => Some(!*user),
349 Cmd::Reload { user, .. } => Some(!*user),
350 Cmd::Status { user, .. } => Some(!*user),
351 _ => None,
352 }
353 }
354
355 #[cfg(not(target_os = "linux"))]
356 pub fn use_su(&self) -> Option<bool> {
357 None
358 }
359}
360
361impl Display for Cmd {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 match self {
364 Cmd::Install { .. } => write!(f, "install"),
365 Cmd::ReinstallAll => write!(f, "reinstall-all"),
366 Cmd::Remove { .. } => write!(f, "remove"),
367 Cmd::Upgrade { .. } => write!(f, "upgrade"),
368 Cmd::Search { .. } => write!(f, "search"),
369 Cmd::Info { .. } => write!(f, "info"),
370 Cmd::Update => write!(f, "update"),
371 Cmd::List { .. } => write!(f, "list"),
372 Cmd::ListVendors => write!(f, "list-vendors"),
373 Cmd::Move { .. } => write!(f, "move"),
374 #[cfg(target_os = "linux")]
375 Cmd::Enable { user, service } if *user => write!(f, "enable --user {service}"),
376 #[cfg(target_os = "linux")]
377 Cmd::Enable { service, .. } => write!(f, "enable {service}"),
378 #[cfg(target_os = "linux")]
379 Cmd::Disable { user, service } if *user => write!(f, "disable --user {service}"),
380 #[cfg(target_os = "linux")]
381 Cmd::Disable { service, .. } => write!(f, "disable {service}"),
382 #[cfg(target_os = "linux")]
383 Cmd::Start { user, service } if *user => write!(f, "start --user {service}"),
384 #[cfg(target_os = "linux")]
385 Cmd::Start { service, .. } => write!(f, "start {service}"),
386 #[cfg(target_os = "linux")]
387 Cmd::Stop { user, service } if *user => write!(f, "stop --user {service}"),
388 #[cfg(target_os = "linux")]
389 Cmd::Stop { service, .. } => write!(f, "stop --user {service}"),
390 #[cfg(target_os = "linux")]
391 Cmd::Restart { user, service } if *user => write!(f, "restart --user {service}"),
392 #[cfg(target_os = "linux")]
393 Cmd::Restart { service, .. } => write!(f, "restart {service}"),
394 #[cfg(target_os = "linux")]
395 Cmd::Status { user, service } if *user => write!(f, "status --user {service}"),
396 #[cfg(target_os = "linux")]
397 Cmd::Status { service, .. } => write!(f, "status {service}"),
398 #[cfg(target_os = "linux")]
399 Cmd::Reload { user } if *user => write!(f, "daemon-reload --user"),
400 #[cfg(target_os = "linux")]
401 Cmd::Reload { .. } => write!(f, "daemon-reload"),
402 }
403 }
404}
405
406impl From<&Cmd> for PlsCommand {
407 fn from(value: &Cmd) -> Self {
408 match value {
409 Cmd::Install {..} => PlsCommand::Install,
410 Cmd::ReinstallAll => PlsCommand::ReinstallAll,
411 Cmd::Remove {..} => PlsCommand::Remove,
412 Cmd::Upgrade {args} if args.is_empty() => PlsCommand::UpgradeAll,
413 Cmd::Upgrade {..} => PlsCommand::Upgrade,
414 Cmd::Search {..} => PlsCommand::Search,
415 Cmd::Info {..} => PlsCommand::Info,
416 Cmd::Update => PlsCommand::Update,
417 Cmd::List { .. } |
418 Cmd::ListVendors => PlsCommand::List,
419 Cmd::Move { args: FromToken::From { origin, to: ToToken::To { destination } } } => PlsCommand::Move {
420 origin: origin.clone(),
421 destination: destination.clone(),
422 },
423 #[cfg(target_os = "linux")]
424 Cmd::Enable { user, service } => PlsCommand::Enable { user: *user, service: service.clone() },
425 #[cfg(target_os = "linux")]
426 Cmd::Disable { user, service } => PlsCommand::Disable { user: *user, service: service.clone() },
427 #[cfg(target_os = "linux")]
428 Cmd::Start { user, service } => PlsCommand::Start { user: *user, service: service.clone() },
429 #[cfg(target_os = "linux")]
430 Cmd::Stop { user, service } => PlsCommand::Stop { user: *user, service: service.clone() },
431 #[cfg(target_os = "linux")]
432 Cmd::Restart { user, service } => PlsCommand::Restart { user: *user, service: service.clone() },
433 #[cfg(target_os = "linux")]
434 Cmd::Status { user, service } => PlsCommand::Status { user: *user, service: service.clone() },
435 #[cfg(target_os = "linux")]
436 Cmd::Reload { user } => PlsCommand::Reload { user: *user },
437 }
438 }
439}
440
441#[cfg(target_os = "windows")]
442const XDG_CONFIG_HOME: &str = "APPDATA";
443#[cfg(not(target_os = "windows"))]
444const XDG_CONFIG_HOME: &str = "XDG_CONFIG_HOME";
445
446#[cfg(target_os = "windows")]
447const CONFIG_HOME: &str = "AppData";
448#[cfg(not(target_os = "windows"))]
449const CONFIG_HOME: &str = ".config";