diff --git a/Cargo.toml b/Cargo.toml index 20d3588..8bab9ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,8 @@ codegen-units = 1 [dependencies] mimalloc = { version = "0.1.27", default-features = false } -clap = { version = "2.34.0", default-features = false, features = ["suggestions"] } +clap = { version = "3.1.9", features = [ "derive", "wrap_help"] } +clap_complete = "3.1.1" regex = { version = "1.5.4", default-features = false, features = ["std", "unicode-perl"] } runas = "0.2.1" rusqlite = { version = "0.26.3", default-features = false } diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..30de242 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,85 @@ +use clap::{Parser, Subcommand}; + +#[derive(Debug, Clone, Parser)] +#[clap(name="Amethyst", version=env!("CARGO_PKG_VERSION"), about=env!("CARGO_PKG_DESCRIPTION"))] +pub struct Args { + #[clap(subcommand)] + pub subcommand: Option, + + /// Sets the level of verbosity + #[clap(long, short, parse(from_occurrences))] + pub verbose: usize, + + /// Complete operation without prompting user + #[clap(long = "noconfirm")] + pub no_confirm: bool, +} + +#[derive(Debug, Clone, Subcommand)] +pub enum Operation { + /// Installs a package from either the AUR or the PacMan-defined repositories + #[clap(name="install", aliases=&["i", "-S"])] + Install(InstallArgs), + + /// Removes a previously installed package + #[clap(name="remove", aliases=&["rm", "-R", "Rs"])] + Remove(RemoveArgs), + + /// Searches for the relevant packages in both the AUR and repos + #[clap(name="search", aliases=&["sea", "-Ss"])] + Search(SearchArgs), + + /// Queries installed packages + #[clap(name="query", aliases=&["ls", "-Q"])] + Query(QueryArgs), + + /// Upgrades locally installed packages to their latest versions + #[clap(name="upgrade", aliases=&["upg", "-Syu"])] + Upgrade, +} + +impl Default for Operation { + fn default() -> Self { + Self::Upgrade + } +} + +#[derive(Default, Debug, Clone, Parser)] +pub struct InstallArgs { + /// The name of the package(s) to install + #[clap(required = true)] + pub packages: Vec, +} + +#[derive(Default, Debug, Clone, Parser)] +pub struct RemoveArgs { + /// The name of the package(s) to remove + #[clap(required = true)] + pub packages: Vec, +} + +#[derive(Default, Debug, Clone, Parser)] +pub struct SearchArgs { + /// Searches for the relevant packages in both the AUR and repos + #[clap(long, short)] + pub aur: bool, + + /// Searches only local repos for the package + #[clap(long, short)] + pub repo: bool, + + /// The string the package must match in the search + #[clap(required = true)] + pub search: Vec, +} + +#[derive(Default, Debug, Clone, Parser)] +pub struct QueryArgs { + /// Lists AUR/foreign packages + #[clap(long, short)] + pub aur: bool, + + /// Lists repo/native packages + #[clap(long, short)] + pub repo: bool, +} diff --git a/src/main.rs b/src/main.rs index 0953eef..24b1c28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,16 @@ -use std::process::{exit, Command}; -use std::{env, io, process}; +use clap::Parser; +use std::process; +use std::process::Command; -use clap::{App, AppSettings, Arg, ArgMatches, ArgSettings, Shell, SubCommand}; +use crate::args::{InstallArgs, Operation, QueryArgs, RemoveArgs, SearchArgs}; +use args::Args; use crate::internal::{crash, info, init, log, sort, structs::Options}; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; +mod args; mod database; mod internal; mod operations; @@ -21,118 +24,10 @@ fn main() { crash("Running amethyst as root is disallowed as it can lead to system breakage. Instead, amethyst will prompt you when it needs superuser permissions".to_string(), 1); } - fn build_app() -> App<'static, 'static> { - let app = App::new("Amethyst") - .version(env!("CARGO_PKG_VERSION")) - .about(env!("CARGO_PKG_DESCRIPTION")) - .arg( - Arg::with_name("verbose") - .short("v") - .long("verbose") - .multiple(true) - .set(ArgSettings::Global) - .help("Sets the level of verbosity"), - ) - .arg( - Arg::with_name("noconfirm") - .long("noconfirm") - .set(ArgSettings::Global) - .help("Complete operation without prompting user"), - ) - .subcommand( - SubCommand::with_name("install") - .about( - "Installs a package from either the AUR or the PacMan-defined repositories", - ) - .aliases(&["-S", "ins"]) - .arg( - Arg::with_name("package(s)") - .help("The name of the package(s) to install") - .required(true) - .multiple(true) - .index(1), - ), - ) - .subcommand( - SubCommand::with_name("remove") - .about("Removes a previously installed package") - .aliases(&["-R", "-Rs", "rm"]) - .arg( - Arg::with_name("package(s)") - .help("The name of the package(s) to remove") - .required(true) - .multiple(true) - .index(1), - ), - ) - .subcommand( - SubCommand::with_name("search") - .about("Searches for the relevant packages in both the AUR and repos") - .aliases(&["-Ss", "sea"]) - .arg( - Arg::with_name("aur") - .short("a") - .long("aur") - .help("Search only the AUR for the package"), - ) - .arg( - Arg::with_name("repo") - .short("r") - .long("repo") - .help("Searches only local repos for the package"), - ) - .arg( - Arg::with_name("package(s)") - .help("The name of the package to search for") - .required(true) - .multiple(false) - .index(1), - ), - ) - .subcommand( - SubCommand::with_name("query") - .about("Queries installed packages") - .aliases(&["-Q", "ls"]) - .arg( - Arg::with_name("aur") - .short("a") - .help("Lists AUR/foreign packages"), - ) - .arg( - Arg::with_name("repo") - .short("r") - .help("Lists repo/native packages"), - ), - ) - .subcommand( - SubCommand::with_name("upgrade") - .about("Upgrades locally installed packages to their latest versions") - .aliases(&["-Syu", "upg"]), - ) - .subcommand( - SubCommand::with_name("compgen") - .about("Generates shell completions for given shell (bash by default)") - .aliases(&["-G", "cg"]) - .arg( - Arg::with_name("shell") - .help("The name of the shell you want to generate completions for") - .possible_values(&["bash", "fish", "zsh", "pwsh", "elvish"]) - .required(true), - ), - ) - .settings(&[ - AppSettings::GlobalVersion, - AppSettings::VersionlessSubcommands, - AppSettings::ArgRequiredElseHelp, - AppSettings::InferSubcommands, - ]); - app - } - - let matches = build_app().get_matches(); + let args: Args = Args::parse(); - let verbosity: i32 = matches.occurrences_of("verbose") as i32; - let noconfirm: bool = matches.is_present("noconfirm"); + let verbosity = args.verbose as i32; + let noconfirm = args.no_confirm; let options = Options { verbosity, @@ -142,174 +37,104 @@ fn main() { init(options); - fn collect_matches(a: &ArgMatches) -> Vec { - a.subcommand() - .1 - .unwrap() - .values_of("package(s)") - .unwrap() - .into_iter() - .map(|s| s.to_string()) - .collect() + match args.subcommand.unwrap_or_default() { + Operation::Install(install_args) => cmd_install(install_args, options), + Operation::Remove(remove_args) => cmd_remove(remove_args, options), + Operation::Search(search_args) => cmd_search(search_args, options), + Operation::Query(query_args) => cmd_query(query_args), + Operation::Upgrade => { + info("Performing system upgrade".to_string()); + operations::upgrade(options); + } } +} - if let true = matches.is_present("install") { - let packages = collect_matches(&matches); - let sorted = sort(&packages, options); +fn cmd_install(args: InstallArgs, options: Options) { + let packages = args.packages; + let sorted = sort(&packages, options); - info(format!( - "Attempting to install packages: {}", - packages.join(", ") + info(format!( + "Attempting to install packages: {}", + packages.join(", ") + )); + + if !sorted.repo.is_empty() { + operations::install(sorted.repo, options); + } + if !sorted.aur.is_empty() { + operations::aur_install(sorted.aur, options); + } + if !sorted.nf.is_empty() { + log(format!( + "Couldn't find packages: {} in repos or the AUR", + sorted.nf.join(", ") )); + } - if !sorted.repo.is_empty() { - operations::install(sorted.repo, options); - } - if !sorted.aur.is_empty() { - operations::aur_install(sorted.aur, options); - } - if !sorted.nf.is_empty() { - log(format!( - "Couldn't find packages: {} in repos or the AUR", - sorted.nf.join(", ") - )); - } + let out = process::Command::new("bash") + .args(&["-c", "sudo find /etc -name *.pacnew"]) + .output() + .expect("Something has gone wrong") + .stdout; - let out = process::Command::new("bash") - .args(&["-c", "sudo find /etc -name *.pacnew"]) - .output() - .expect("Something has gone wrong") - .stdout; + if !String::from_utf8((*out).to_owned()).unwrap().is_empty() { + info(format!("You have .pacnew files in /etc ({}) that you haven't removed or acted upon, it is recommended you do that now", String::from_utf8((*out).to_owned()).unwrap().split_whitespace().collect::>().join(", "))); + } +} - if !String::from_utf8((*out).to_owned()).unwrap().is_empty() { - info(format!("You have .pacnew files in /etc ({}) that you haven't removed or acted upon, it is recommended you do that now", String::from_utf8((*out).to_owned()).unwrap().split_whitespace().collect::>().join(", "))); - } +fn cmd_remove(args: RemoveArgs, options: Options) { + let packages = args.packages; + info(format!("Uninstalling packages: {}", &packages.join(", "))); + operations::uninstall(packages, options); +} - exit(0); +fn cmd_search(args: SearchArgs, options: Options) { + let query_string = args.search.join(" "); + if args.aur { + info(format!("Searching AUR for {}", &query_string)); + operations::aur_search(&query_string, options); } - - if let true = matches.is_present("remove") { - let packages = collect_matches(&matches); - info(format!("Uninstalling packages: {}", &packages.join(", "))); - operations::uninstall(packages, options); - exit(0); + if args.repo { + info(format!("Searching repos for {}", &query_string)); + operations::search(&query_string, options); } - if let true = matches.is_present("upgrade") { - info("Performing system upgrade".to_string()); - operations::upgrade(options); - exit(0); + if !args.aur && !args.repo { + info(format!("Searching AUR and repos for {}", &query_string)); + operations::search(&query_string, options); + operations::aur_search(&query_string, options); } +} - if let true = matches.is_present("search") { - let packages = collect_matches(&matches); - if matches - .subcommand_matches("search") - .unwrap() - .is_present("aur") - { - info(format!("Searching AUR for {}", &packages[0])); - operations::aur_search(&packages[0], options); - } - if matches - .subcommand_matches("search") - .unwrap() - .is_present("repo") - { - info(format!("Searching repos for {}", &packages[0])); - operations::search(&packages[0], options); - } - - if !matches - .subcommand_matches("search") - .unwrap() - .is_present("repo") - && !matches - .subcommand_matches("search") - .unwrap() - .is_present("aur") - { - info(format!("Searching AUR and repos for {}", &packages[0])); - operations::search(&packages[0], options); - operations::aur_search(&packages[0], options); - } - exit(0); +fn cmd_query(args: QueryArgs) { + if args.aur { + Command::new("pacman") + .arg("-Qm") + .spawn() + .expect("Something has gone wrong") + .wait() + .unwrap(); } - - if let true = matches.is_present("query") { - if matches - .subcommand_matches("query") - .unwrap() - .is_present("aur") - { - Command::new("pacman") - .arg("-Qm") - .spawn() - .expect("Something has gone wrong") - .wait() - .unwrap(); - } - if matches - .subcommand_matches("query") - .unwrap() - .is_present("repo") - { - Command::new("pacman") - .arg("-Qn") - .spawn() - .expect("Something has gone wrong") - .wait() - .unwrap(); - } - if !matches - .subcommand_matches("query") - .unwrap() - .is_present("aur") - && !matches - .subcommand_matches("query") - .unwrap() - .is_present("repo") - { - Command::new("pacman") - .arg("-Qn") - .spawn() - .expect("Something has gone wrong") - .wait() - .unwrap(); - Command::new("pacman") - .arg("-Qm") - .spawn() - .expect("Something has gone wrong") - .wait() - .unwrap(); - } - exit(0); + if args.repo { + Command::new("pacman") + .arg("-Qn") + .spawn() + .expect("Something has gone wrong") + .wait() + .unwrap(); } - - if let true = &matches.is_present("compgen") { - let mut app = build_app(); - match matches - .subcommand_matches("compgen") - .unwrap() - .value_of("shell") - .unwrap() - { - "bash" => { - app.gen_completions_to("ame", Shell::Bash, &mut io::stdout()); - } - "fish" => { - app.gen_completions_to("ame", Shell::Fish, &mut io::stdout()); - } - "zsh" => { - app.gen_completions_to("ame", Shell::Zsh, &mut io::stdout()); - } - "pwsh" => { - app.gen_completions_to("ame", Shell::PowerShell, &mut io::stdout()); - } - "elvish" => { - app.gen_completions_to("ame", Shell::Elvish, &mut io::stdout()); - } - _ => {} - } + if !args.repo && !args.aur { + Command::new("pacman") + .arg("-Qn") + .spawn() + .expect("Something has gone wrong") + .wait() + .unwrap(); + Command::new("pacman") + .arg("-Qm") + .spawn() + .expect("Something has gone wrong") + .wait() + .unwrap(); } }