please_install/
params.rs

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    /// skip settings
15    #[arg(short = 'x', long, global = true, action = ArgAction::SetTrue)]
16    pub skip_settings: bool,
17
18    /// configuration file
19    #[arg(short, long, global = true)]
20    pub config: Option<String>,
21
22    /// dry run (do not actually execute commands)
23    #[arg(short = 'n', long, global = true, action = ArgAction::SetTrue)]
24    pub dry_run: bool,
25
26    /// assume yes for all prompts
27    #[arg(short, long, global = true, action = ArgAction::SetTrue)]
28    pub yes: bool,
29
30    #[cfg(not(target_os = "windows"))]
31    /// run as root (in case user must be sudoer)
32    #[arg(long, global = true, action = ArgAction::SetTrue)]
33    pub su: bool,
34
35    /// set the installer command
36    #[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    /// install package(s)
48    #[command()]
49    Install {
50        /// package(s) to be installed
51        #[arg(name = "PACKAGE")]
52        args: Vec<String>,
53    },
54
55    /// reinstall known installed package(s)
56    #[command()]
57    ReinstallAll,
58
59    /// remove package(s)
60    #[command(alias = "uninstall")]
61    Remove {
62        /// package(s) to be removed
63        #[arg(name = "PACKAGE")]
64        args: Vec<String>,
65    },
66
67    /// upgrade package(s)
68    #[command()]
69    Upgrade {
70        /// package(s) to be upgraded
71        #[arg(name = "PACKAGE")]
72        args: Vec<String>,
73    },
74
75    /// search for package(s)
76    #[command()]
77    Search {
78        /// text to be searched
79        #[arg(name = "QUERY")]
80        args: String,
81
82        /// paginate results
83        #[arg(short, long, action = ArgAction::SetTrue)]
84        paginate: bool,
85
86        #[arg(skip)]
87        pager: Option<String>,
88    },
89
90    /// get info for a package
91    #[command()]
92    Info {
93        /// package for which to get info
94        #[arg(name = "PACKAGE")]
95        args: String,
96    },
97
98    /// update database
99    #[command()]
100    Update,
101
102    /// list installed packages
103    #[command()]
104    List {
105        /// paginate results
106        #[arg(short, long, action = ArgAction::SetTrue)]
107        paginate: bool,
108
109        #[arg(skip)]
110        pager: Option<String>,
111    },
112
113    /// list available vendors
114    #[command()]
115    ListVendors,
116
117    /// move package to another vendor
118    #[command()]
119    Move {
120
121        #[command(subcommand)]
122        args: FromToken,
123    },
124
125    #[cfg(target_os = "linux")]
126    /// enable service
127    #[command()]
128    Enable {
129        /// service in user space
130        #[arg(short, long, action = ArgAction::SetTrue)]
131        user: bool,
132
133        /// service to enable
134        #[arg(name = "SERVICE")]
135        service: String,
136    },
137
138    #[cfg(target_os = "linux")]
139    /// disable service
140    #[command()]
141    Disable {
142        /// service in user space
143        #[arg(short, long, action = ArgAction::SetTrue)]
144        user: bool,
145
146        /// service to disable
147        #[arg(name = "SERVICE")]
148        service: String,
149    },
150
151    #[cfg(target_os = "linux")]
152    /// start service
153    #[command()]
154    Start {
155        /// service in user space
156        #[arg(short, long, action = ArgAction::SetTrue)]
157        user: bool,
158
159        /// service to start
160        #[arg(name = "SERVICE")]
161        service: String,
162    },
163
164    #[cfg(target_os = "linux")]
165    /// stop service
166    #[command()]
167    Stop {
168        /// service in user space
169        #[arg(short, long, action = ArgAction::SetTrue)]
170        user: bool,
171
172        /// service to stop
173        #[arg(name = "SERVICE")]
174        service: String,
175    },
176
177    #[cfg(target_os = "linux")]
178    /// restart service
179    #[command()]
180    Restart {
181        /// service in user space
182        #[arg(short, long, action = ArgAction::SetTrue)]
183        user: bool,
184
185        /// service to restart
186        #[arg(name = "SERVICE")]
187        service: String,
188    },
189
190    #[cfg(target_os = "linux")]
191    /// status of service
192    #[command()]
193    Status {
194        /// service in user space
195        #[arg(short, long, action = ArgAction::SetTrue)]
196        user: bool,
197
198        /// service to check status
199        #[arg(name = "SERVICE")]
200        service: String,
201    },
202
203    #[cfg(target_os = "linux")]
204    /// reload daemon
205    #[command()]
206    Reload {
207        /// service in user space
208        #[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        /// original vendor from which the packages will be moved from
220        #[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        /// destination vendor
234        #[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";