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, 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 (user must be sudoer)
32    #[arg(short, long, global = true, action = ArgAction::SetTrue)]
33    pub su: bool,
34
35    /// set the installer command
36    #[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    /// 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
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}