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
126#[derive(Clone, Debug, PartialEq, Subcommand)]
127pub enum FromToken {
128
129    #[command()]
130    From {
131        /// original vendor from which the packages will be moved from
132        #[arg(name = "ORIGIN")]
133        origin: Vendor,
134
135        #[command(subcommand)]
136        to: ToToken,
137    }
138}
139
140#[derive(Clone, Debug, PartialEq, Subcommand)]
141pub enum ToToken {
142
143    #[command()]
144    To {
145        /// destination vendor
146        #[arg(name = "DESTINATION")]
147        destination: Vendor,
148    }
149}
150
151impl Params {
152    pub fn config(mut self) -> Self {
153        if self.skip_settings {
154            return self;
155        }
156
157        let config = match &self.config {
158            Some(config) => PathBuf::from(config),
159            None => {
160                let config_home = match env::var(XDG_CONFIG_HOME) {
161                    Ok(config_home) => PathBuf::from(config_home),
162                    Err(_) => PathBuf::from(env!["HOME"]).join(CONFIG_HOME),
163                };
164                config_home.join("please.toml")
165            }
166        };
167        if config.exists() {
168            let _ = self.load(config);
169        }
170
171        self
172    }
173
174    pub fn actual_vendor(&self) -> Result<Vendor> {
175        match self.vendor.or(self.default_vendor) {
176            Some(vendor) => Ok(vendor),
177            None => Vendor::new(),
178        }
179    }
180
181    fn load(&mut self, config: PathBuf) -> Result<()> {
182        let mut file = File::open(config)?;
183        let mut content = String::new();
184        file.read_to_string(&mut content)?;
185        let mut defaults: Table = content.parse()?;
186        let cmd = self.cmd.to_string();
187        let bind = defaults.clone();
188        if let Some(value) = bind.get(cmd.as_str()).and_then(|value| value.as_table()) {
189            for (k, v) in value.iter() {
190                defaults.insert(k.to_string(), v.clone());
191            }
192        }
193
194        match &self.cmd {
195            Cmd::Search { args, .. } => {
196                if defaults.get("pager").is_some() {
197                    self.cmd = Cmd::Search {
198                        pager: defaults
199                            .get("pager")
200                            .and_then(|pager| pager.as_str())
201                            .map(|pager| pager.to_owned())
202                            .filter(|pager| !pager.is_empty())
203                            .map(|pager| pager.replace("$args", args)),
204                        paginate: true,
205                        args: args.to_owned(),
206                    }
207                }
208            }
209            Cmd::List { .. } => {
210                if defaults.get("pager").is_some() {
211                    self.cmd = Cmd::List {
212                        pager: defaults
213                            .get("pager")
214                            .and_then(|pager| pager.as_str())
215                            .map(|pager| pager.to_owned())
216                            .filter(|pager| !pager.is_empty()),
217                        paginate: true,
218                    }
219                }
220            }
221            _ => (),
222        }
223
224        if defaults.get("assume-yes").and_then(|yes| yes.as_bool()).unwrap_or_default() {
225            self.yes = true;
226        }
227        if defaults.get("su").and_then(|yes| yes.as_bool()).unwrap_or_default() {
228            self.su = true;
229        }
230        if self.vendor.is_none() {
231            if let Some(vendor) = defaults.get("vendor").and_then(|vendor| vendor.as_str()) {
232                let vendor: Vendor = vendor.try_into()?;
233                self.default_vendor = Some(vendor);
234            }
235        }
236
237        Ok(())
238    }
239}
240
241impl Cmd {
242    pub fn args(&self) -> Vec<String> {
243        match self {
244            Cmd::Install { args } => args.clone(),
245            Cmd::Remove { args } => args.clone(),
246            Cmd::Upgrade { args } => args.clone(),
247            Cmd::Search { args, .. } => vec![args.to_string()],
248            Cmd::Info { args } => vec![args.to_string()],
249            _ => Vec::new(),
250        }
251    }
252}
253
254impl Display for Cmd {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        match self {
257            Cmd::Install { .. } => write!(f, "install"),
258            Cmd::ReinstallAll => write!(f, "reinstall-all"),
259            Cmd::Remove { .. } => write!(f, "remove"),
260            Cmd::Upgrade { .. } => write!(f, "upgrade"),
261            Cmd::Search { .. } => write!(f, "search"),
262            Cmd::Info { .. } => write!(f, "info"),
263            Cmd::Update => write!(f, "update"),
264            Cmd::List { .. } => write!(f, "list"),
265            Cmd::ListVendors => write!(f, "list-vendors"),
266            Cmd::Move { .. } => write!(f, "move"),
267        }
268    }
269}
270
271impl From<&Cmd> for PlsCommand {
272    fn from(value: &Cmd) -> Self {
273        match value {
274            Cmd::Install {..} => PlsCommand::Install,
275            Cmd::ReinstallAll => PlsCommand::ReinstallAll,
276            Cmd::Remove {..} => PlsCommand::Remove,
277            Cmd::Upgrade {args} if args.is_empty() => PlsCommand::UpgradeAll,
278            Cmd::Upgrade {..} => PlsCommand::Upgrade,
279            Cmd::Search {..} => PlsCommand::Search,
280            Cmd::Info {..} => PlsCommand::Info,
281            Cmd::Update => PlsCommand::Update,
282            Cmd::List { .. } |
283            Cmd::ListVendors => PlsCommand::List,
284            Cmd::Move { args: FromToken::From { origin, to: ToToken::To { destination } } } => PlsCommand::Move {
285                origin: origin.clone(),
286                destination: destination.clone(),
287            },
288        }
289    }
290}
291
292#[cfg(target_os = "windows")]
293const XDG_CONFIG_HOME: &str = "APPDATA";
294#[cfg(not(target_os = "windows"))]
295const XDG_CONFIG_HOME: &str = "XDG_CONFIG_HOME";
296
297#[cfg(target_os = "windows")]
298const CONFIG_HOME: &str = "AppData";
299#[cfg(not(target_os = "windows"))]
300const CONFIG_HOME: &str = ".config";