use std::{env, fmt::Display, fs::File, io::Read, path::PathBuf};
use clap::{ArgAction, Parser, Subcommand};
use eyre::Result;
use toml::Table;
use crate::pls_command::PlsCommand;
use crate::vendors::Vendor;
#[derive(Debug, Parser)]
#[command(about, author, name = "please", version)]
pub struct Params {
#[arg(short = 'x', long, global = true, action = ArgAction::SetTrue)]
pub skip_settings: bool,
#[arg(short, long, global = true)]
pub config: Option<String>,
#[arg(short = 'n', long, global = true, action = ArgAction::SetTrue)]
pub dry_run: bool,
#[arg(short, long, global = true, action = ArgAction::SetTrue)]
pub yes: bool,
#[cfg(not(target_os = "windows"))]
#[arg(long, global = true, action = ArgAction::SetTrue)]
pub su: bool,
#[arg(long, global = true)]
pub vendor: Option<Vendor>,
#[command(subcommand)]
pub cmd: Cmd,
pub default_vendor: Option<Vendor>,
}
#[derive(Clone, Debug, PartialEq, Subcommand)]
pub enum Cmd {
#[command()]
Install {
#[arg(name = "PACKAGE")]
args: Vec<String>,
},
#[command()]
ReinstallAll,
#[command(alias = "uninstall")]
Remove {
#[arg(name = "PACKAGE")]
args: Vec<String>,
},
#[command()]
Upgrade {
#[arg(name = "PACKAGE")]
args: Vec<String>,
},
#[command()]
Search {
#[arg(name = "QUERY")]
args: String,
#[arg(short, long, action = ArgAction::SetTrue)]
paginate: bool,
#[arg(skip)]
pager: Option<String>,
},
#[command()]
Info {
#[arg(name = "PACKAGE")]
args: String,
},
#[command()]
Update,
#[command()]
List {
#[arg(short, long, action = ArgAction::SetTrue)]
paginate: bool,
#[arg(skip)]
pager: Option<String>,
},
#[command()]
ListVendors,
#[command()]
Move {
#[command(subcommand)]
args: FromToken,
},
#[cfg(target_os = "linux")]
#[command()]
Enable {
#[arg(short, long, action = ArgAction::SetTrue)]
user: bool,
#[arg(name = "SERVICE")]
service: String,
},
#[cfg(target_os = "linux")]
#[command()]
Disable {
#[arg(short, long, action = ArgAction::SetTrue)]
user: bool,
#[arg(name = "SERVICE")]
service: String,
},
#[cfg(target_os = "linux")]
#[command()]
Start {
#[arg(short, long, action = ArgAction::SetTrue)]
user: bool,
#[arg(name = "SERVICE")]
service: String,
},
#[cfg(target_os = "linux")]
#[command()]
Stop {
#[arg(short, long, action = ArgAction::SetTrue)]
user: bool,
#[arg(name = "SERVICE")]
service: String,
},
#[cfg(target_os = "linux")]
#[command()]
Restart {
#[arg(short, long, action = ArgAction::SetTrue)]
user: bool,
#[arg(name = "SERVICE")]
service: String,
},
#[cfg(target_os = "linux")]
#[command()]
Status {
#[arg(short, long, action = ArgAction::SetTrue)]
user: bool,
#[arg(name = "SERVICE")]
service: String,
},
#[cfg(target_os = "linux")]
#[command()]
Reload {
#[arg(short, long, action = ArgAction::SetTrue)]
user: bool,
},
}
#[derive(Clone, Debug, PartialEq, Subcommand)]
pub enum FromToken {
#[command()]
From {
#[arg(name = "ORIGIN")]
origin: Vendor,
#[command(subcommand)]
to: ToToken,
}
}
#[derive(Clone, Debug, PartialEq, Subcommand)]
pub enum ToToken {
#[command()]
To {
#[arg(name = "DESTINATION")]
destination: Vendor,
}
}
impl Params {
pub fn config(mut self) -> Self {
if self.skip_settings {
return self;
}
let config = match &self.config {
Some(config) => PathBuf::from(config),
None => {
let config_home = match env::var(XDG_CONFIG_HOME) {
Ok(config_home) => PathBuf::from(config_home),
Err(_) => PathBuf::from(env!["HOME"]).join(CONFIG_HOME),
};
config_home.join("please.toml")
}
};
if config.exists() {
let _ = self.load(config);
}
self
}
pub fn actual_vendor(&self) -> Result<Vendor> {
match self.vendor.or(self.default_vendor) {
Some(vendor) => Ok(vendor),
None => Vendor::new(),
}
}
fn load(&mut self, config: PathBuf) -> Result<()> {
let mut file = File::open(config)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
let mut defaults: Table = content.parse()?;
let cmd = self.cmd.to_string();
let bind = defaults.clone();
if let Some(value) = bind.get(cmd.as_str()).and_then(|value| value.as_table()) {
for (k, v) in value.iter() {
defaults.insert(k.to_string(), v.clone());
}
}
match &self.cmd {
Cmd::Search { args, .. } => {
if defaults.get("pager").is_some() {
self.cmd = Cmd::Search {
pager: defaults
.get("pager")
.and_then(|pager| pager.as_str())
.map(|pager| pager.to_owned())
.filter(|pager| !pager.is_empty())
.map(|pager| pager.replace("$args", args)),
paginate: true,
args: args.to_owned(),
}
}
}
Cmd::List { .. } => {
if defaults.get("pager").is_some() {
self.cmd = Cmd::List {
pager: defaults
.get("pager")
.and_then(|pager| pager.as_str())
.map(|pager| pager.to_owned())
.filter(|pager| !pager.is_empty()),
paginate: true,
}
}
}
_ => (),
}
if defaults.get("assume-yes").and_then(|yes| yes.as_bool()).unwrap_or_default() {
self.yes = true;
}
if defaults.get("su").and_then(|yes| yes.as_bool()).unwrap_or_default() {
self.su = true;
}
if self.vendor.is_none() {
if let Some(vendor) = defaults.get("vendor").and_then(|vendor| vendor.as_str()) {
let vendor: Vendor = vendor.try_into()?;
self.default_vendor = Some(vendor);
}
}
Ok(())
}
}
impl Cmd {
pub fn args(&self) -> Vec<String> {
match self {
Cmd::Install { args } => args.clone(),
Cmd::Remove { args } => args.clone(),
Cmd::Upgrade { args } => args.clone(),
Cmd::Search { args, .. } => vec![args.to_string()],
Cmd::Info { args } => vec![args.to_string()],
_ => Vec::new(),
}
}
#[cfg(target_os = "linux")]
pub fn use_su(&self) -> Option<bool> {
match self {
Cmd::Enable { user, .. } => Some(!*user),
Cmd::Disable { user, .. } => Some(!*user),
Cmd::Start { user, .. } => Some(!*user),
Cmd::Stop { user, .. } => Some(!*user),
Cmd::Restart { user, .. } => Some(!*user),
Cmd::Reload { user, .. } => Some(!*user),
Cmd::Status { user, .. } => Some(!*user),
_ => None,
}
}
#[cfg(not(target_os = "linux"))]
pub fn use_su(&self) -> Option<bool> {
None
}
}
impl Display for Cmd {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Cmd::Install { .. } => write!(f, "install"),
Cmd::ReinstallAll => write!(f, "reinstall-all"),
Cmd::Remove { .. } => write!(f, "remove"),
Cmd::Upgrade { .. } => write!(f, "upgrade"),
Cmd::Search { .. } => write!(f, "search"),
Cmd::Info { .. } => write!(f, "info"),
Cmd::Update => write!(f, "update"),
Cmd::List { .. } => write!(f, "list"),
Cmd::ListVendors => write!(f, "list-vendors"),
Cmd::Move { .. } => write!(f, "move"),
#[cfg(target_os = "linux")]
Cmd::Enable { user, service } if *user => write!(f, "enable --user {service}"),
#[cfg(target_os = "linux")]
Cmd::Enable { service, .. } => write!(f, "enable {service}"),
#[cfg(target_os = "linux")]
Cmd::Disable { user, service } if *user => write!(f, "disable --user {service}"),
#[cfg(target_os = "linux")]
Cmd::Disable { service, .. } => write!(f, "disable {service}"),
#[cfg(target_os = "linux")]
Cmd::Start { user, service } if *user => write!(f, "start --user {service}"),
#[cfg(target_os = "linux")]
Cmd::Start { service, .. } => write!(f, "start {service}"),
#[cfg(target_os = "linux")]
Cmd::Stop { user, service } if *user => write!(f, "stop --user {service}"),
#[cfg(target_os = "linux")]
Cmd::Stop { service, .. } => write!(f, "stop --user {service}"),
#[cfg(target_os = "linux")]
Cmd::Restart { user, service } if *user => write!(f, "restart --user {service}"),
#[cfg(target_os = "linux")]
Cmd::Restart { service, .. } => write!(f, "restart {service}"),
#[cfg(target_os = "linux")]
Cmd::Status { user, service } if *user => write!(f, "status --user {service}"),
#[cfg(target_os = "linux")]
Cmd::Status { service, .. } => write!(f, "status {service}"),
#[cfg(target_os = "linux")]
Cmd::Reload { user } if *user => write!(f, "daemon-reload --user"),
#[cfg(target_os = "linux")]
Cmd::Reload { .. } => write!(f, "daemon-reload"),
}
}
}
impl From<&Cmd> for PlsCommand {
fn from(value: &Cmd) -> Self {
match value {
Cmd::Install {..} => PlsCommand::Install,
Cmd::ReinstallAll => PlsCommand::ReinstallAll,
Cmd::Remove {..} => PlsCommand::Remove,
Cmd::Upgrade {args} if args.is_empty() => PlsCommand::UpgradeAll,
Cmd::Upgrade {..} => PlsCommand::Upgrade,
Cmd::Search {..} => PlsCommand::Search,
Cmd::Info {..} => PlsCommand::Info,
Cmd::Update => PlsCommand::Update,
Cmd::List { .. } |
Cmd::ListVendors => PlsCommand::List,
Cmd::Move { args: FromToken::From { origin, to: ToToken::To { destination } } } => PlsCommand::Move {
origin: origin.clone(),
destination: destination.clone(),
},
#[cfg(target_os = "linux")]
Cmd::Enable { user, service } => PlsCommand::Enable { user: *user, service: service.clone() },
#[cfg(target_os = "linux")]
Cmd::Disable { user, service } => PlsCommand::Disable { user: *user, service: service.clone() },
#[cfg(target_os = "linux")]
Cmd::Start { user, service } => PlsCommand::Start { user: *user, service: service.clone() },
#[cfg(target_os = "linux")]
Cmd::Stop { user, service } => PlsCommand::Stop { user: *user, service: service.clone() },
#[cfg(target_os = "linux")]
Cmd::Restart { user, service } => PlsCommand::Restart { user: *user, service: service.clone() },
#[cfg(target_os = "linux")]
Cmd::Status { user, service } => PlsCommand::Status { user: *user, service: service.clone() },
#[cfg(target_os = "linux")]
Cmd::Reload { user } => PlsCommand::Reload { user: *user },
}
}
}
#[cfg(target_os = "windows")]
const XDG_CONFIG_HOME: &str = "APPDATA";
#[cfg(not(target_os = "windows"))]
const XDG_CONFIG_HOME: &str = "XDG_CONFIG_HOME";
#[cfg(target_os = "windows")]
const CONFIG_HOME: &str = "AppData";
#[cfg(not(target_os = "windows"))]
const CONFIG_HOME: &str = ".config";