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
126#[derive(Clone, Debug, PartialEq, Subcommand)]
127pub enum FromToken {
128
129 #[command()]
130 From {
131 #[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 #[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";