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 #[arg(short = 'x', long, global = true, action = ArgAction::SetTrue)]
13 pub skip_settings: bool,
14
15 #[arg(short, long, global = true)]
17 pub config: Option<String>,
18
19 #[arg(short, long, global = true, action = ArgAction::SetTrue)]
21 pub dry_run: bool,
22
23 #[arg(short, long, global = true, action = ArgAction::SetTrue)]
25 pub yes: bool,
26
27 #[cfg(not(target_os = "windows"))]
28 #[arg(short, long, global = true, action = ArgAction::SetTrue)]
30 pub su: bool,
31
32 #[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 #[command()]
44 Install {
45 #[arg(name = "PACKAGE")]
47 args: Vec<String>,
48 },
49
50 #[command()]
52 Remove {
53 #[arg(name = "PACKAGE")]
55 args: Vec<String>,
56 },
57
58 #[command()]
60 Upgrade {
61 #[arg(name = "PACKAGE")]
63 args: Vec<String>,
64 },
65
66 #[command()]
68 Search {
69 #[arg(name = "QUERY")]
71 args: String,
72
73 #[arg(short, long, action = ArgAction::SetTrue)]
75 paginate: bool,
76
77 #[arg(skip)]
78 pager: Option<String>,
79 },
80
81 #[command()]
83 Info {
84 #[arg(name = "PACKAGE")]
86 args: String,
87 },
88
89 #[command()]
91 Update,
92
93 #[command()]
95 List {
96 #[arg(short, long, action = ArgAction::SetTrue)]
98 paginate: bool,
99
100 #[arg(skip)]
101 pager: Option<String>,
102 },
103
104 #[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}