please_install/
params.rs

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