diff --git a/Cargo.lock b/Cargo.lock index fcf2fc8..e26500c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,6 +214,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" dependencies = [ "clap_builder", + "clap_derive", + "once_cell", ] [[package]] @@ -230,6 +232,18 @@ dependencies = [ "terminal_size", ] +[[package]] +name = "clap_derive" +version = "4.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "0.2.4" @@ -666,6 +680,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index 4f07a2b..1cf8e9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,9 @@ members = ["crates/libscoop", "crates/scoop_hash"] [lib] path = "src/lib.rs" -[[bin]] -name = "hok" -path = "src/main.rs" - [dependencies] anyhow = "1.0" -clap = { version = "4.3", features = ["wrap_help", "cargo"] } +clap = { version = "4.3", features = ["wrap_help", "cargo", "derive"] } crossterm = "0.28" env_logger = "0.8.3" indicatif = "0.17.5" diff --git a/src/bin/hok.rs b/src/bin/hok.rs new file mode 100644 index 0000000..f1983bc --- /dev/null +++ b/src/bin/hok.rs @@ -0,0 +1,3 @@ +pub fn main() { + std::process::exit(hok::create_app() as i32); +} diff --git a/src/clap_app.rs b/src/clap_app.rs deleted file mode 100644 index 86c0794..0000000 --- a/src/clap_app.rs +++ /dev/null @@ -1,431 +0,0 @@ -use clap::{crate_description, crate_name, crate_version, Arg, ArgAction, Command}; - -pub fn build() -> Command { - Command::new(crate_name!()) - .version(crate_version!()) - .about(crate_description!()) - .after_help(format!( - "Type '{} help ' to get help for a specific command.", - crate_name!() - )) - .subcommand_required(true) - .arg_required_else_help(true) - .max_term_width(100) - .subcommand( - Command::new("bucket") - .about("Manage manifest buckets") - .arg_required_else_help(true) - .subcommand( - Command::new("add") - .about("Add a bucket") - .arg_required_else_help(true) - .arg( - Arg::new("name") - .help("The bucket name") - .index(1) - .required(true), - ) - .arg( - Arg::new("repo") - .help("The bucket repository url (optional for known buckets)") - .index(2), - ), - ) - .subcommand( - Command::new("list").about("List buckets").alias("ls").arg( - Arg::new("known") - .help("List known buckets") - .short('k') - .long("known") - .action(ArgAction::SetTrue), - ), - ) - .subcommand( - Command::new("remove") - .about("Remove bucket(s)") - .alias("rm") - .arg_required_else_help(true) - .arg( - Arg::new("name") - .help("The bucket name") - .required(true) - .action(ArgAction::Append), - ), - ), - ) - .subcommand( - Command::new("cache") - .about("Package cache management") - .arg_required_else_help(true) - .subcommand( - Command::new("list") - .about("List download caches") - .alias("ls") - .arg( - Arg::new("query") - .help("List caches matching the query") - .index(1), - ), - ) - .subcommand( - Command::new("remove") - .about("Remove download caches") - .alias("rm") - .arg_required_else_help(true) - .arg(Arg::new("query").help("Remove caches matching the query")) - .arg( - Arg::new("all") - .help("Remove all caches") - .short('a') - .long("all") - .action(ArgAction::SetTrue) - .conflicts_with("query"), - ), - ), - ) - .subcommand( - Command::new("cat") - .about("Inspect the manifest of a package") - .arg_required_else_help(true) - .arg( - Arg::new("package") - .help("Name of the package to be inspected") - .required(true), - ), - ) - .subcommand( - Command::new("cleanup") - .about("Cleanup apps by removing old versions") - .arg_required_else_help(true) - .arg( - Arg::new("app") - .help("Given named app(s) to be cleaned up") - .action(ArgAction::Append), - ) - .arg( - Arg::new("cache") - .help("Remove download cache simultaneously") - .short('k') - .long("cache") - .action(ArgAction::SetTrue), - ), - ) - .subcommand( - Command::new("config") - .about("Configuration management") - .arg_required_else_help(true) - .subcommand( - Command::new("edit").about("Edit the config file [default editor: notepad]"), - ) - .subcommand( - Command::new("list") - .alias("ls") - .about("List all settings in key-value"), - ) - .subcommand( - Command::new("set") - .about("Add a new setting to the config file") - .arg_required_else_help(true) - .arg(Arg::new("key").help("The key of the config").required(true)) - .arg( - Arg::new("value") - .help("The value of the setting") - .required(true), - ), - ) - .subcommand( - Command::new("unset") - .about("Remove a setting from config file") - .arg_required_else_help(true) - .arg( - Arg::new("key") - .help("The key of the setting") - .required(true), - ), - ), - ) - .subcommand( - Command::new("hold") - .about("Hold package(s) to disable changes") - .arg_required_else_help(true) - .arg( - Arg::new("package") - .help("The package(s) to be held") - .required(true) - .action(ArgAction::Append), - ), - ) - .subcommand( - Command::new("home") - .about("Browse the homepage of a package") - .arg_required_else_help(true) - .arg(Arg::new("package").help("The package name").required(true)), - ) - .subcommand( - Command::new("info") - .about("Show package(s) basic information") - .arg_required_else_help(true) - .arg( - Arg::new("query") - .help("The query string (regex supported)") - .required(true), - ), - ) - .subcommand( - Command::new("install") - .about("Install package(s)") - .alias("i") - .arg_required_else_help(true) - .arg( - Arg::new("package") - .help("The package(s) to install") - .required(true) - .action(ArgAction::Append), - ) - .arg( - Arg::new("download-only") - .help("Download package(s) without performing installation") - .short('d') - .long("download-only") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("ignore-failure") - .help("Ignore failures to ensure a complete transaction") - .short('f') - .long("ignore-failure") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("offline") - .help("Leverage cache and suppress network access") - .short('o') - .long("offline") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("assume-yes") - .help("Assume yes to all prompts and run non-interactively") - .short('y') - .long("assume-yes") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("ignore-cache") - .help("Ignore cache and force download") - .short('D') - .long("ignore-cache") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("independent") - .help("Do not install dependencies (may break packages)") - .short('I') - .long("independent") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("no-replace") - .help("Do not replace package(s)") - .short('R') - .long("no-replace") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("escape-hold") - .help("Escape hold to allow changes on held package(s)") - .short('S') - .long("escape-hold") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("no-upgrade") - .help("Do not upgrade package(s)") - .short('U') - .long("no-upgrade") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("no-hash-check") - .help("Skip package integrity check") - .long("no-hash-check") - .action(ArgAction::SetTrue), - ), - ) - .subcommand( - Command::new("list") - .about("List installed package(s)") - .arg( - Arg::new("query") - .help("The query string (regex supported by default)") - .action(ArgAction::Append), - ) - .arg( - Arg::new("explicit") - .help("Turn regex off and use explicit matching") - .short('e') - .long("explicit") - .requires("query") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("upgradable") - .help("List upgradable package(s)") - .short('u') - .long("upgradable") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("held") - .help("List held package(s)") - .short('H') - .long("held") - .action(ArgAction::SetTrue), - ), - ) - .subcommand( - Command::new("search") - .about("Search available package(s)") - .alias("s") - .long_about( - "Search available package(s) from synced buckets.\n\ - The query is performed against package names by default, \ - use --with-description or --with-binary to search through \ - package descriptions or binaries.", - ) - .arg_required_else_help(true) - .arg( - Arg::new("query") - .help("The query string (regex supported by default)") - .required(true) - .action(ArgAction::Append), - ) - .arg( - Arg::new("explicit") - .help("Turn regex off and use explicit matching") - .short('e') - .long("explicit") - .action(ArgAction::SetTrue) - .conflicts_with_all(["with-description", "with-binary"]), - ) - .arg( - Arg::new("with-binary") - .help("Search through package binaries as well") - .short('B') - .long("with-binary") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("with-description") - .help("Search through package descriptions as well") - .short('D') - .long("with-description") - .action(ArgAction::SetTrue), - ), - ) - .subcommand( - Command::new("unhold") - .about("Unhold package(s) to enable changes") - .arg_required_else_help(true) - .arg( - Arg::new("package") - .help("The package(s) to be unheld") - .required(true) - .action(ArgAction::Append), - ), - ) - .subcommand( - Command::new("uninstall") - .about("Uninstall package(s)") - .alias("rm") - .alias("remove") - .arg_required_else_help(true) - .arg( - Arg::new("package") - .help("The package(s) to uninstall") - .required(true) - .action(ArgAction::Append), - ) - .arg( - Arg::new("cascade") - .help("Remove unneeded dependencies as well") - .short('c') - .long("cascade") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("purge") - .help("Purge package(s) persistent data as well") - .short('p') - .long("purge") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("assume-yes") - .help("Assume yes to all prompts and run non-interactively") - .short('y') - .long("assume-yes") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("no-dependent-check") - .help("Disable dependent check (may break other packages)") - .long("no-dependent-check") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("escape-hold") - .help("Escape hold to allow to uninstall held package(s)") - .short('S') - .long("escape-hold") - .action(ArgAction::SetTrue), - ), - ) - .subcommand( - Command::new("update") - .about("Fetch and update subscribed buckets") - .alias("u"), - ) - .subcommand( - Command::new("upgrade") - .about("Upgrade installed package(s)") - .arg( - Arg::new("package") - .help("The package(s) to be upgraded (default: all except held)"), - ) - .arg( - Arg::new("ignore-failure") - .help("Ignore failures to ensure a complete transaction") - .short('f') - .long("ignore-failure") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("offline") - .help("Leverage cache and suppress network access") - .short('o') - .long("offline") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("assume-yes") - .help("Assume yes to all prompts and run non-interactively") - .short('y') - .long("assume-yes") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("escape-hold") - .help("Escape hold to allow to upgrade held package(s)") - .short('S') - .long("escape-hold") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("no-hash-check") - .help("Skip package integrity check") - .long("no-hash-check") - .action(ArgAction::SetTrue), - ), - ) -} diff --git a/src/cmd/bucket.rs b/src/cmd/bucket.rs index d460326..57d6825 100644 --- a/src/cmd/bucket.rs +++ b/src/cmd/bucket.rs @@ -1,24 +1,51 @@ -use clap::ArgMatches; +use clap::{ArgAction, Parser, Subcommand}; use crossterm::style::Stylize; use libscoop::{operation, Session}; use std::io::{stdout, Write}; use crate::Result; -pub fn cmd_bucket(matches: &ArgMatches, session: &Session) -> Result<()> { - match matches.subcommand() { - Some(("add", args)) => { - let name = args - .get_one::("name") - .map(|s| s.as_str()) - .unwrap_or_default(); - let repo = args - .get_one::("repo") - .map(|s| s.as_str()) - .unwrap_or_default(); +/// Manage manifest buckets +#[derive(Debug, Parser)] +pub struct Args { + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +pub enum Command { + /// Add a bucket + #[clap(arg_required_else_help = true)] + Add { + /// The bucket name + name: String, + /// The bucket repository url (optional for known buckets) + repo: Option, + }, + /// List buckets + #[clap(alias = "ls")] + List { + /// List known buckets + #[arg(short = 'k', long, action = ArgAction::SetTrue)] + known: bool, + }, + /// Remove bucket(s) + #[clap(alias = "rm")] + #[clap(arg_required_else_help = true)] + Remove { + /// The bucket name(s) + #[arg(required = true, action = ArgAction::Append)] + name: Vec, + }, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { + match args.command { + Command::Add { name, repo } => { print!("Adding bucket {}... ", name); let _ = stdout().flush(); - match operation::bucket_add(session, name, repo) { + let repo = repo.as_deref().unwrap_or_default(); + match operation::bucket_add(session, name.as_str(), repo) { Ok(..) => println!("{}", "Ok".green()), Err(err) => { println!("{}", "Err".red()); @@ -27,8 +54,7 @@ pub fn cmd_bucket(matches: &ArgMatches, session: &Session) -> Result<()> { } Ok(()) } - Some(("list", args)) => { - let known = args.get_flag("known"); + Command::List { known } => { if known { for (name, repo) in operation::bucket_list_known() { println!("{} {}", name.green(), repo); @@ -51,15 +77,11 @@ pub fn cmd_bucket(matches: &ArgMatches, session: &Session) -> Result<()> { } } } - Some(("remove", args)) => { - let names = args - .get_many::("name") - .map(|v| v.map(|s| s.as_str()).collect::>()) - .unwrap_or_default(); - for name in names { + Command::Remove { name } => { + for name in name { print!("Removing bucket {}... ", name); let _ = stdout().flush(); - match operation::bucket_remove(session, name) { + match operation::bucket_remove(session, name.as_str()) { Ok(..) => println!("{}", "Ok".green()), Err(err) => { println!("{}", "Err".red()); @@ -69,6 +91,5 @@ pub fn cmd_bucket(matches: &ArgMatches, session: &Session) -> Result<()> { } Ok(()) } - _ => unreachable!(), } } diff --git a/src/cmd/cache.rs b/src/cmd/cache.rs index bc4e30c..2c0b076 100644 --- a/src/cmd/cache.rs +++ b/src/cmd/cache.rs @@ -1,16 +1,40 @@ -use clap::ArgMatches; +use clap::{ArgAction, Parser, Subcommand}; use libscoop::{operation, Session}; use crate::{util, Result}; -pub fn cmd_cache(matches: &ArgMatches, session: &Session) -> Result<()> { - match matches.subcommand() { - Some(("list", args)) => { - let query = args - .get_one::("query") - .map(|s| s.as_ref()) - .unwrap_or("*"); - let files = operation::cache_list(session, query)?; +/// Package cache management +#[derive(Debug, Parser)] +pub struct Args { + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +pub enum Command { + /// List download caches + #[clap(alias = "ls")] + List { + /// List caches matching the query + query: Option, + }, + /// Remove download caches + #[clap(alias = "rm")] + #[clap(arg_required_else_help = true)] + Remove { + /// Remove caches matching the query + query: Option, + /// Remove all caches + #[arg(short, long, action = ArgAction::SetTrue, conflicts_with = "query")] + all: bool, + }, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { + match args.command { + Command::List { query } => { + let query = query.unwrap_or("*".to_string()); + let files = operation::cache_list(session, query.as_str())?; let mut total_size: u64 = 0; let total_count = files.len(); @@ -36,15 +60,19 @@ pub fn cmd_cache(matches: &ArgMatches, session: &Session) -> Result<()> { Ok(()) } - Some(("remove", args)) => { - if args.get_flag("all") { + Command::Remove { query, all } => { + if all { match operation::cache_remove(session, "*") { - Ok(_) => println!("All download caches were removed."), + Ok(_) => { + println!("All download caches were removed."); + return Ok(()); + } Err(e) => return Err(e.into()), } } - if let Some(query) = args.get_one::("query").map(|s| s.as_ref()) { - match operation::cache_remove(session, query) { + + if let Some(query) = query { + match operation::cache_remove(session, query.as_str()) { Ok(_) => { if query == "*" { println!("All download caches were removed."); @@ -55,8 +83,8 @@ pub fn cmd_cache(matches: &ArgMatches, session: &Session) -> Result<()> { Err(e) => return Err(e.into()), } } + Ok(()) } - _ => unreachable!(), } } diff --git a/src/cmd/cat.rs b/src/cmd/cat.rs index 3ff5cd2..a77b1cb 100644 --- a/src/cmd/cat.rs +++ b/src/cmd/cat.rs @@ -1,74 +1,82 @@ -use clap::ArgMatches; +use clap::Parser; use crossterm::style::Stylize; use libscoop::{operation, QueryOption, Session}; use std::{io::Write, path::Path, process::Command}; use crate::Result; -pub fn cmd_cat(matches: &ArgMatches, session: &Session) -> Result<()> { - if let Some(query) = matches.get_one::("package") { - let queries = vec![query.as_str()]; - let options = vec![QueryOption::Explicit]; - let mut result = operation::package_query(session, queries, options, false)?; +/// Inspect the manifest of a package +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = true)] +pub struct Args { + /// Name of the package to be inspected + package: String, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { + let query = args.package; + + let queries = vec![query.as_str()]; + let options = vec![QueryOption::Explicit]; + let mut result = operation::package_query(session, queries, options, false)?; - if result.is_empty() { - eprintln!("Could not find package named '{}'.", query) + if result.is_empty() { + eprintln!("Could not find package named '{}'.", query) + } else { + let length = result.len(); + let package = if length == 1 { + &result[0] } else { - let length = result.len(); - let package = if length == 1 { - &result[0] - } else { - result.sort_by_key(|p| p.ident()); + result.sort_by_key(|p| p.ident()); - println!("Found multiple packages named '{}':\n", query); - for (idx, pkg) in result.iter().enumerate() { - println!( - " {}. {}/{} ({})", - idx, - pkg.bucket(), - pkg.name(), - pkg.homepage() - ); - } - print!("\nPlease select one, enter the number to continue: "); - std::io::stdout().flush().unwrap(); - let mut input = String::new(); - std::io::stdin().read_line(&mut input).unwrap(); - let parsed = input.trim().parse::(); - if parsed.is_err() { - eprintln!("Invalid input."); - return Ok(()); - } + println!("Found multiple packages named '{}':\n", query); + for (idx, pkg) in result.iter().enumerate() { + println!( + " {}. {}/{} ({})", + idx, + pkg.bucket(), + pkg.name(), + pkg.homepage() + ); + } + print!("\nPlease select one, enter the number to continue: "); + std::io::stdout().flush().unwrap(); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + let parsed = input.trim().parse::(); + if parsed.is_err() { + eprintln!("Invalid input."); + return Ok(()); + } - let num = parsed.unwrap(); - if num >= length { - eprintln!("Invalid input."); - return Ok(()); - } - &result[num] - }; + let num = parsed.unwrap(); + if num >= length { + eprintln!("Invalid input."); + return Ok(()); + } + &result[num] + }; - let path = package.manifest().path(); - println!("{}:", path.display().to_string().green()); - match is_program_available("bat.exe") { - false => { - let content = std::fs::read_to_string(path)?; - println!("{}", content.trim()); + let path = package.manifest().path(); + println!("{}:", path.display().to_string().green()); + match is_program_available("bat.exe") { + false => { + let content = std::fs::read_to_string(path)?; + println!("{}", content.trim()); + } + true => { + let config = session.config(); + let mut cat_args = vec!["--no-paging"]; + let cat_style = config.cat_style(); + if !cat_style.is_empty() { + cat_args.push("--style"); + cat_args.push(cat_style); } - true => { - let config = session.config(); - let mut cat_args = vec!["--no-paging"]; - let cat_style = config.cat_style(); - if !cat_style.is_empty() { - cat_args.push("--style"); - cat_args.push(cat_style); - } - cat_args.push("--language"); - cat_args.push("json"); + cat_args.push("--language"); + cat_args.push("json"); - let mut child = Command::new("bat.exe").arg(path).args(cat_args).spawn()?; - child.wait()?; - } + let mut child = Command::new("bat.exe").arg(path).args(cat_args).spawn()?; + child.wait()?; } } } diff --git a/src/cmd/cleanup.rs b/src/cmd/cleanup.rs index 0367077..e1dbf2f 100644 --- a/src/cmd/cleanup.rs +++ b/src/cmd/cleanup.rs @@ -1,5 +1,5 @@ #![allow(unused)] -use clap::ArgMatches; +use clap::{ArgAction, ArgMatches, Parser}; use crossterm::style::Stylize; use libscoop::{operation, Session}; use std::{ @@ -10,16 +10,24 @@ use std::{ use crate::Result; -pub fn cmd_cleanup(matches: &ArgMatches, session: &Session) -> Result<()> { +/// Cleanup apps by removing old versions +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = true)] +pub struct Args { + /// Given named app(s) to be cleaned up + #[arg(action = ArgAction::Append)] + app: Vec, + /// Remove download cache simultaneously + #[arg(short = 'k', long, action = ArgAction::SetTrue)] + cache: bool, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { let config = session.config(); let apps_path = config.root_path().join("apps"); // let running_apps = running_apps(&apps_path); - let query = matches - .get_many::("app") - .map(|v| v.map(|s| s.as_str()).collect::>()) - .unwrap_or_default() - .join(" "); + let query = args.app.join(" "); eprintln!("Not implemented yet."); diff --git a/src/cmd/config.rs b/src/cmd/config.rs index 4f24f11..6ad6ffe 100644 --- a/src/cmd/config.rs +++ b/src/cmd/config.rs @@ -1,48 +1,64 @@ -use clap::ArgMatches; +use clap::{Parser, Subcommand}; use crossterm::style::Stylize; use libscoop::{operation, Session}; -use std::process::Command; use crate::Result; -pub fn cmd_config(matches: &ArgMatches, session: &Session) -> Result<()> { - match matches.subcommand() { - Some(("edit", _)) => { +/// Configuration management +#[derive(Debug, Parser)] +pub struct Args { + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +pub enum Command { + /// Edit the config file [default editor: notepad] + Edit, + /// List all settings in key-value + #[clap(alias = "ls")] + List, + /// Add a new setting to the config file + #[clap(arg_required_else_help = true)] + Set { + /// The key of the config + key: String, + /// The value of the setting + value: String, + }, + /// Remove a setting from config file + #[clap(arg_required_else_help = true)] + Unset { + /// The key of the setting + key: String, + }, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { + match args.command { + Command::Edit => { let editor = std::env::var("EDITOR").unwrap_or_else(|_| "notepad".to_string()); - let mut child = Command::new(editor.as_str()) + let mut child = std::process::Command::new(editor.as_str()) .arg(&session.config().path) .spawn()?; child.wait()?; Ok(()) } - Some(("list", _)) => { + Command::List => { let config_json = operation::config_list(session)?; println!("{}:", &session.config().path.display().to_string().green()); println!("{}", config_json); Ok(()) } - Some(("set", args)) => { - let key = args - .get_one::("key") - .map(|s| s.as_str()) - .unwrap_or_default(); - let value = args - .get_one::("value") - .map(|s| s.as_str()) - .unwrap_or_default(); - operation::config_set(session, key, value)?; + Command::Set { key, value } => { + operation::config_set(session, key.as_str(), value.as_str())?; println!("Config '{}' has been set to '{}'", key, value); Ok(()) } - Some(("unset", args)) => { - let key = args - .get_one::("key") - .map(|s| s.as_str()) - .unwrap_or_default(); - operation::config_set(session, key, "")?; + Command::Unset { key } => { + operation::config_set(session, key.as_str(), "")?; println!("Config '{}' has been unset", key); Ok(()) } - _ => unreachable!(), } } diff --git a/src/cmd/hold.rs b/src/cmd/hold.rs index 7229d25..8645e17 100644 --- a/src/cmd/hold.rs +++ b/src/cmd/hold.rs @@ -1,16 +1,20 @@ -use clap::ArgMatches; +use clap::{ArgAction, Parser}; use crossterm::style::Stylize; use libscoop::{operation, Session}; use crate::Result; -pub fn cmd_hold(matches: &ArgMatches, session: &Session) -> Result<()> { - let packages = matches - .get_many::("package") - .map(|v| v.map(|s| s.as_str()).collect::>()) - .unwrap_or_default(); +/// Hold package(s) to disable changes +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = true)] +pub struct Args { + /// The package(s) to be held + #[arg(required= true, action = ArgAction::Append)] + package: Vec, +} - for name in packages { +pub fn execute(args: Args, session: &Session) -> Result<()> { + for name in &args.package { print!("Holding {}...", name); match operation::package_hold(session, name, true) { Ok(..) => { diff --git a/src/cmd/home.rs b/src/cmd/home.rs index 3092cfb..f0bf51f 100644 --- a/src/cmd/home.rs +++ b/src/cmd/home.rs @@ -1,58 +1,66 @@ -use clap::ArgMatches; +use clap::Parser; use libscoop::{operation, QueryOption, Session}; use std::{io::Write, process::Command}; use crate::Result; -pub fn cmd_home(matches: &ArgMatches, session: &Session) -> Result<()> { - if let Some(query) = matches.get_one::("package") { - let queries = vec![query.as_str()]; - let options = vec![QueryOption::Explicit]; - let mut result = operation::package_query(session, queries, options, false)?; +/// Browse the homepage of a package +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = true)] +pub struct Args { + /// The package name + package: String, +} - match result.len() { - 0 => eprintln!("Could not find package named '{}'.", query), - 1 => { - let package = &result[0]; - let url = std::ffi::OsStr::new(package.homepage()); - Command::new("cmd") - .arg("/C") - .arg("start") - .arg(url) - .spawn()?; - } - _ => { - result.sort_by_key(|p| p.ident()); +pub fn execute(args: Args, session: &Session) -> Result<()> { + let query = args.package; - println!("Found multiple packages named '{}':\n", query); - for (idx, pkg) in result.iter().enumerate() { - println!( - " {}. {}/{} ({})", - idx, - pkg.bucket(), - pkg.name(), - pkg.homepage() - ); - } - print!("\nPlease select one, enter the number to continue: "); - std::io::stdout().flush().unwrap(); - let mut input = String::new(); - std::io::stdin().read_line(&mut input).unwrap(); - let parsed = input.trim().parse::(); - if let Ok(num) = parsed { - if num < result.len() { - let package = &result[num]; - let url = std::ffi::OsStr::new(package.homepage()); - Command::new("cmd") - .arg("/C") - .arg("start") - .arg(url) - .spawn()?; - return Ok(()); - } + let queries = vec![query.as_str()]; + let options = vec![QueryOption::Explicit]; + let mut result = operation::package_query(session, queries, options, false)?; + + match result.len() { + 0 => eprintln!("Could not find package named '{}'.", query), + 1 => { + let package = &result[0]; + let url = std::ffi::OsStr::new(package.homepage()); + Command::new("cmd") + .arg("/C") + .arg("start") + .arg(url) + .spawn()?; + } + _ => { + result.sort_by_key(|p| p.ident()); + + println!("Found multiple packages named '{}':\n", query); + for (idx, pkg) in result.iter().enumerate() { + println!( + " {}. {}/{} ({})", + idx, + pkg.bucket(), + pkg.name(), + pkg.homepage() + ); + } + print!("\nPlease select one, enter the number to continue: "); + std::io::stdout().flush().unwrap(); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + let parsed = input.trim().parse::(); + if let Ok(num) = parsed { + if num < result.len() { + let package = &result[num]; + let url = std::ffi::OsStr::new(package.homepage()); + Command::new("cmd") + .arg("/C") + .arg("start") + .arg(url) + .spawn()?; + return Ok(()); } - eprintln!("Invalid input."); } + eprintln!("Invalid input."); } } Ok(()) diff --git a/src/cmd/info.rs b/src/cmd/info.rs index bd36b7f..c63c2d7 100644 --- a/src/cmd/info.rs +++ b/src/cmd/info.rs @@ -1,52 +1,60 @@ -use clap::ArgMatches; +use clap::Parser; use libscoop::{operation, Session}; use crate::Result; -pub fn cmd_info(matches: &ArgMatches, session: &Session) -> Result<()> { - if let Some(query) = matches.get_one::("query") { - let queries = vec![query.as_str()]; - let options = vec![]; - let packages = operation::package_query(session, queries, options, false)?; - let length = packages.len(); - match length { - 0 => eprintln!("Could not find package for query '{}'.", query), - _ => { - if length == 1 { - println!("Found 1 package for query '{}':", query); - } else { - println!("Found {} package(s) for query '{}':", length, query); - } +/// Show package(s) basic information +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = true)] +pub struct Args { + /// The query string (regex supported) + query: String, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { + let query = args.query; + + let queries = vec![query.as_str()]; + let options = vec![]; + let packages = operation::package_query(session, queries, options, false)?; + let length = packages.len(); + match length { + 0 => eprintln!("Could not find package for query '{}'.", query), + _ => { + if length == 1 { + println!("Found 1 package for query '{}':", query); + } else { + println!("Found {} package(s) for query '{}':", length, query); + } - for (idx, pkg) in packages.iter().enumerate() { - // Ident - println!("Identity: {}", pkg.ident()); - // Name - println!("Name: {}", pkg.name()); - // Bucket - println!("Bucket: {}", pkg.bucket()); - // Description - println!( - "Description: {}", - pkg.description().unwrap_or("") - ); - // Version - println!("Version: {}", pkg.version()); - // Homepage - println!("Homepage: {}", pkg.homepage()); - // License - println!("License: {}", pkg.license()); - // Binaries - println!( - "Shims: {}", - pkg.shims() - .map(|v| v.join(",")) - .unwrap_or("".to_owned()) - ); + for (idx, pkg) in packages.iter().enumerate() { + // Ident + println!("Identity: {}", pkg.ident()); + // Name + println!("Name: {}", pkg.name()); + // Bucket + println!("Bucket: {}", pkg.bucket()); + // Description + println!( + "Description: {}", + pkg.description().unwrap_or("") + ); + // Version + println!("Version: {}", pkg.version()); + // Homepage + println!("Homepage: {}", pkg.homepage()); + // License + println!("License: {}", pkg.license()); + // Binaries + println!( + "Shims: {}", + pkg.shims() + .map(|v| v.join(",")) + .unwrap_or("".to_owned()) + ); - if idx != (length - 1) { - println!(); - } + if idx != (length - 1) { + println!(); } } } diff --git a/src/cmd/install.rs b/src/cmd/install.rs index 458eacb..6cb63c9 100644 --- a/src/cmd/install.rs +++ b/src/cmd/install.rs @@ -1,5 +1,5 @@ #![allow(unused_assignments)] -use clap::ArgMatches; +use clap::{ArgAction, Parser}; use crossterm::{ cursor, style::Stylize, @@ -11,50 +11,85 @@ use std::io::Write; use crate::{cui, util, Result}; -pub fn cmd_install(matches: &ArgMatches, session: &Session) -> Result<()> { +/// Install package(s) +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = true)] +pub struct Args { + /// The package(s) to install + #[arg(required = true, action = ArgAction::Append)] + package: Vec, + /// Download package(s) without performing installation + #[arg(short = 'd', long, action = ArgAction::SetTrue)] + download_only: bool, + /// Ignore failures to ensure a complete transaction + #[arg(short = 'f', long, action = ArgAction::SetTrue)] + ignore_failure: bool, + /// Leverage cache and suppress network access + #[arg(short = 'o', long, action = ArgAction::SetTrue)] + offline: bool, + /// Assume yes to all prompts and run non-interactively + #[arg(short = 'y', long, action = ArgAction::SetTrue)] + assume_yes: bool, + /// Ignore cache and force download + #[arg(short = 'D', long, action = ArgAction::SetTrue)] + ignore_cache: bool, + /// Do not install dependencies (may break packages) + #[arg(short = 'I', long, action = ArgAction::SetTrue)] + independent: bool, + /// Do not replace package(s) + #[arg(short = 'R', long, action = ArgAction::SetTrue)] + no_replace: bool, + /// Escape hold to allow changes on held package(s) + #[arg(short = 'S', long, action = ArgAction::SetTrue)] + escape_hold: bool, + /// Do not upgrade package(s) + #[arg(short = 'U', long, action = ArgAction::SetTrue)] + no_upgrade: bool, + /// Skip package integrity check + #[arg(long, action = ArgAction::SetTrue)] + no_hash_check: bool, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { let mut options = vec![]; - let queries = matches - .get_many::("package") - .map(|v| v.map(|s| s.as_str()).collect::>()) - .unwrap_or_default(); - if matches.get_flag("assume-yes") { + if args.assume_yes { options.push(SyncOption::AssumeYes); } - if matches.get_flag("download-only") { + if args.download_only { options.push(SyncOption::DownloadOnly); } - if matches.get_flag("escape-hold") { + if args.escape_hold { options.push(SyncOption::EscapeHold); } - if matches.get_flag("ignore-failure") { + if args.ignore_failure { options.push(SyncOption::IgnoreFailure); } - if matches.get_flag("ignore-cache") { + if args.ignore_cache { options.push(SyncOption::IgnoreCache); } - if matches.get_flag("no-upgrade") { + if args.no_upgrade { options.push(SyncOption::NoUpgrade); } - if matches.get_flag("no-replace") { + if args.no_replace { options.push(SyncOption::NoReplace); } - if matches.get_flag("offline") { + if args.offline { options.push(SyncOption::Offline); } - if matches.get_flag("independent") { + if args.independent { options.push(SyncOption::NoDependencies); } - if matches.get_flag("no-hash-check") { + if args.no_hash_check { options.push(SyncOption::NoHashCheck); } @@ -218,6 +253,7 @@ pub fn cmd_install(matches: &ArgMatches, session: &Session) -> Result<()> { } }); + let queries = args.package.iter().map(|s| s.as_str()).collect::>(); operation::package_sync(session, queries, options)?; handle.join().unwrap(); diff --git a/src/cmd/list.rs b/src/cmd/list.rs index 5942857..e25b83f 100644 --- a/src/cmd/list.rs +++ b/src/cmd/list.rs @@ -1,24 +1,35 @@ -use clap::ArgMatches; +use clap::{ArgAction, Parser}; use crossterm::style::Stylize; use libscoop::{operation, QueryOption, Session}; use crate::Result; -pub fn cmd_list(matches: &ArgMatches, session: &Session) -> Result<()> { - let queries = matches - .get_many::("query") - .unwrap_or_default() - .map(|s| s.as_str()) - .collect::>(); +/// List installed package(s) +#[derive(Debug, Parser)] +pub struct Args { + /// The query string (regex supported by default) + #[arg(action = ArgAction::Append)] + query: Vec, + /// Turn regex off and use explicit matching + #[arg(short = 'e', long, action = ArgAction::SetTrue)] + explicit: bool, + /// List upgradable package(s) + #[arg(short = 'u', long, action = ArgAction::SetTrue)] + upgradable: bool, + /// List held package(s) + #[arg(short = 'H', long, action = ArgAction::SetTrue)] + held: bool, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { + let queries = args.query.iter().map(|s| s.as_str()).collect::>(); let mut options = vec![]; - let flag_held = matches.get_flag("held"); - let flag_upgradable = matches.get_flag("upgradable"); - if matches.get_flag("explicit") { + if args.explicit { options.push(QueryOption::Explicit); } - if flag_upgradable { + if args.upgradable { options.push(QueryOption::Upgradable); } @@ -32,12 +43,12 @@ pub fn cmd_list(matches: &ArgMatches, session: &Session) -> Result<()> { ); let held = pkg.is_held(); - if flag_held && !held { + if args.held && !held { continue; } let upgradable = pkg.upgradable_version(); - if flag_upgradable && upgradable.is_some() { + if args.upgradable && upgradable.is_some() { output.push_str(format!(" -> {}", upgradable.unwrap().blue()).as_str()); } diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 9778cbb..d28077b 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,3 +1,5 @@ +use clap::{crate_description, crate_name, crate_version, Parser, Subcommand}; + mod bucket; mod cache; mod cat; @@ -14,18 +16,69 @@ mod uninstall; mod update; mod upgrade; -pub(super) use bucket::cmd_bucket; -pub(super) use cache::cmd_cache; -pub(super) use cat::cmd_cat; -pub(super) use cleanup::cmd_cleanup; -pub(super) use config::cmd_config; -pub(super) use hold::cmd_hold; -pub(super) use home::cmd_home; -pub(super) use info::cmd_info; -pub(super) use install::cmd_install; -pub(super) use list::cmd_list; -pub(super) use search::cmd_search; -pub(super) use unhold::cmd_unhold; -pub(super) use uninstall::cmd_uninstall; -pub(super) use update::cmd_update; -pub(super) use upgrade::cmd_upgrade; +use crate::Result; +use libscoop::Session; + +#[derive(Parser)] +#[command( + name = crate_name!(), + version = crate_version!(), + about = crate_description!(), + subcommand_required = true, + arg_required_else_help = true, + max_term_width = 100, + after_help = format!( + "Type '{} help ' to get help for a specific command.", + crate_name!() + ) +)] +pub struct Cli { + #[command(subcommand)] + pub command: Command, +} + +#[derive(Subcommand)] +pub enum Command { + Bucket(bucket::Args), + Cache(cache::Args), + Cat(cat::Args), + Cleanup(cleanup::Args), + Config(config::Args), + Hold(hold::Args), + Home(home::Args), + Info(info::Args), + #[clap(alias = "i")] + Install(install::Args), + List(list::Args), + #[clap(alias = "s")] + Search(search::Args), + Unhold(unhold::Args), + #[clap(alias = "rm", alias = "remove")] + Uninstall(uninstall::Args), + #[clap(alias = "u")] + Update(update::Args), + Upgrade(upgrade::Args), +} + +/// CLI entry point +pub fn start(session: &Session) -> Result<()> { + let args = Cli::parse(); + + match args.command { + Command::Bucket(args) => bucket::execute(args, session), + Command::Cache(args) => cache::execute(args, session), + Command::Cat(args) => cat::execute(args, session), + Command::Cleanup(args) => cleanup::execute(args, session), + Command::Config(args) => config::execute(args, session), + Command::Hold(args) => hold::execute(args, session), + Command::Home(args) => home::execute(args, session), + Command::Info(args) => info::execute(args, session), + Command::Install(args) => install::execute(args, session), + Command::List(args) => list::execute(args, session), + Command::Search(args) => search::execute(args, session), + Command::Unhold(args) => unhold::execute(args, session), + Command::Uninstall(args) => uninstall::execute(args, session), + Command::Update(args) => update::execute(args, session), + Command::Upgrade(args) => upgrade::execute(args, session), + } +} diff --git a/src/cmd/search.rs b/src/cmd/search.rs index 8ea6f26..01aabe4 100644 --- a/src/cmd/search.rs +++ b/src/cmd/search.rs @@ -1,66 +1,85 @@ -use clap::ArgMatches; +use clap::{ArgAction, Parser}; use crossterm::style::Stylize; use libscoop::{operation, QueryOption, Session}; use crate::Result; -pub fn cmd_search(matches: &ArgMatches, session: &Session) -> Result<()> { - if let Some(queries) = matches.get_many::("query") { - let queries = queries.map(|s| s.as_str()).collect::>(); - let mut options = vec![]; - let with_binary = matches.get_flag("with-binary"); - let with_description = matches.get_flag("with-description"); +/// Search available package(s) +/// +/// Search available package(s) from synced buckets. +/// The query is performed against package names by default, use +/// --with-description or --with-binary to search through package +/// descriptions or binaries. +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = true)] +pub struct Args { + /// The query string (regex supported by default) + #[arg(required = true, action = ArgAction::Append)] + query: Vec, + /// Turn regex off and use explicit matching + #[arg(short = 'e', long, action = ArgAction::SetTrue, conflicts_with_all = &["with_binary", "with_description"])] + explicit: bool, + /// Search through package binaries as well + #[arg(short = 'B', long, action = ArgAction::SetTrue)] + with_binary: bool, + /// Search through package descriptions as well + #[arg(short = 'D', long, action = ArgAction::SetTrue)] + with_description: bool, +} - if with_binary { - options.push(QueryOption::Binary); - } +pub fn execute(args: Args, session: &Session) -> Result<()> { + let queries = args.query.iter().map(|s| s.as_str()).collect::>(); + let mut options = vec![]; - if with_description { - options.push(QueryOption::Description); - } + if args.with_binary { + options.push(QueryOption::Binary); + } - if matches.get_flag("explicit") { - options.push(QueryOption::Explicit); - } + if args.with_description { + options.push(QueryOption::Description); + } - let packages = operation::package_query(session, queries, options, false)?; + if args.explicit { + options.push(QueryOption::Explicit); + } - for pkg in packages { - let mut output = String::new(); - output.push_str( - format!("{}/{} {}", pkg.name(), pkg.bucket().green(), pkg.version()).as_str(), - ); + let packages = operation::package_query(session, queries, options, false)?; - if pkg.is_strictly_installed() { - let manifest_version = pkg.version(); - let installed_version = pkg.installed_version().unwrap(); - if manifest_version != installed_version { - output.push_str( - format!(" [installed: {}]", installed_version) - .blue() - .to_string() - .as_str(), - ); - } else { - output.push_str(" [installed]".blue().to_string().as_str()); - } - } + for pkg in packages { + let mut output = String::new(); + output.push_str( + format!("{}/{} {}", pkg.name(), pkg.bucket().green(), pkg.version()).as_str(), + ); - if with_description { - let description = pkg.description().unwrap_or(""); - output.push_str(format!("\n {}", description).as_str()); + if pkg.is_strictly_installed() { + let manifest_version = pkg.version(); + let installed_version = pkg.installed_version().unwrap(); + if manifest_version != installed_version { + output.push_str( + format!(" [installed: {}]", installed_version) + .blue() + .to_string() + .as_str(), + ); + } else { + output.push_str(" [installed]".blue().to_string().as_str()); } + } - if with_binary { - let shims = match pkg.shims() { - None => "".to_owned(), - Some(shims) => shims.join(","), - }; - output.push_str(format!("\n {}", shims).as_str()); - } + if args.with_description { + let description = pkg.description().unwrap_or(""); + output.push_str(format!("\n {}", description).as_str()); + } - println!("{}", output); + if args.with_binary { + let shims = match pkg.shims() { + None => "".to_owned(), + Some(shims) => shims.join(","), + }; + output.push_str(format!("\n {}", shims).as_str()); } + + println!("{}", output); } Ok(()) } diff --git a/src/cmd/unhold.rs b/src/cmd/unhold.rs index 66c9701..70036f2 100644 --- a/src/cmd/unhold.rs +++ b/src/cmd/unhold.rs @@ -1,15 +1,20 @@ -use clap::ArgMatches; +use clap::{ArgAction, Parser}; use crossterm::style::Stylize; use libscoop::{operation, Session}; use crate::Result; -pub fn cmd_unhold(matches: &ArgMatches, session: &Session) -> Result<()> { - let packages = matches - .get_many::("package") - .map(|v| v.map(|s| s.as_str()).collect::>()) - .unwrap_or_default(); +/// Unhold package(s) to enable changes +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = true)] +pub struct Args { + /// The package(s) to be unheld + #[arg(required = true, action = ArgAction::Append)] + package: Vec, +} +pub fn execute(args: Args, session: &Session) -> Result<()> { + let packages = args.package.iter().map(|s| s.as_str()).collect::>(); for name in packages { print!("Unholding {}...", name); match operation::package_hold(session, name, false) { diff --git a/src/cmd/uninstall.rs b/src/cmd/uninstall.rs index 2dd8dc0..ef87a18 100644 --- a/src/cmd/uninstall.rs +++ b/src/cmd/uninstall.rs @@ -1,33 +1,54 @@ -use clap::ArgMatches; +use clap::{ArgAction, Parser}; use crossterm::style::Stylize; use libscoop::{operation, Event, Session, SyncOption}; use crate::{cui, Result}; -pub fn cmd_uninstall(matches: &ArgMatches, session: &Session) -> Result<()> { - let queries = matches - .get_many::("package") - .map(|v| v.map(|s| s.as_str()).collect::>()) - .unwrap_or_default(); +/// Uninstall package(s) +#[derive(Debug, Parser)] +#[clap(arg_required_else_help = true)] +pub struct Args { + /// The package(s) to uninstall + #[arg(required = true, action = ArgAction::Append)] + package: Vec, + /// Remove unneeded dependencies as well + #[arg(short = 'c', long, action = ArgAction::SetTrue)] + cascade: bool, + /// Purge package(s) persistent data as well + #[arg(short = 'p', long, action = ArgAction::SetTrue)] + purge: bool, + /// Assume yes to all prompts and run non-interactively + #[arg(short = 'y', long, action = ArgAction::SetTrue)] + assume_yes: bool, + /// Disable dependent check (may break other packages) + #[arg(long, action = ArgAction::SetTrue)] + no_dependent_check: bool, + /// Escape hold to allow to uninstall held package(s) + #[arg(short = 'S', long, action = ArgAction::SetTrue)] + escape_hold: bool, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { + let queries = args.package.iter().map(|s| s.as_str()).collect::>(); let mut options = vec![SyncOption::Remove]; - if matches.get_flag("assume-yes") { + if args.assume_yes { options.push(SyncOption::AssumeYes); } - if matches.get_flag("cascade") { + if args.cascade { options.push(SyncOption::Cascade); } - if matches.get_flag("no-dependent-check") { + if args.no_dependent_check { options.push(SyncOption::NoDependentCheck); } - if matches.get_flag("escape-hold") { + if args.escape_hold { options.push(SyncOption::EscapeHold); } - if matches.get_flag("purge") { + if args.purge { options.push(SyncOption::Purge); } diff --git a/src/cmd/update.rs b/src/cmd/update.rs index ca5b404..e2a061a 100644 --- a/src/cmd/update.rs +++ b/src/cmd/update.rs @@ -1,9 +1,14 @@ +use clap::Parser; use crossterm::{cursor, ExecutableCommand}; use libscoop::{operation, Event, Session}; use crate::{cui, Result}; -pub fn cmd_update(_: &clap::ArgMatches, session: &Session) -> Result<()> { +/// Fetch and update subscribed buckets +#[derive(Debug, Parser)] +pub struct Args {} + +pub fn execute(_: Args, session: &Session) -> Result<()> { let rx = session.event_bus().receiver(); let handle = std::thread::spawn(move || { diff --git a/src/cmd/upgrade.rs b/src/cmd/upgrade.rs index e360e54..39cb8d1 100644 --- a/src/cmd/upgrade.rs +++ b/src/cmd/upgrade.rs @@ -1,4 +1,4 @@ -use clap::ArgMatches; +use clap::{ArgAction, Parser}; use crossterm::{ cursor, style::Stylize, @@ -9,30 +9,53 @@ use libscoop::{operation, Event, Session, SyncOption}; use crate::{cui, util, Result}; -pub fn cmd_upgrade(matches: &ArgMatches, session: &Session) -> Result<()> { - let queries = matches - .get_many::("package") - .map(|v| v.map(|s| s.as_str()).collect::>()) - .unwrap_or(vec!["*"]); +/// Upgrade installed package(s) +#[derive(Debug, Parser)] +pub struct Args { + /// The package(s) to be upgraded (default: all except held) + #[arg(action = ArgAction::Append)] + package: Vec, + /// Ignore failures to ensure a complete transaction + #[arg(short = 'f', long, action = ArgAction::SetTrue)] + ignore_failure: bool, + /// Leverage cache and suppress network access + #[arg(short = 'o', long, action = ArgAction::SetTrue)] + offline: bool, + /// Assume yes to all prompts and run non-interactively + #[arg(short = 'y', long, action = ArgAction::SetTrue)] + assume_yes: bool, + /// Escape hold to allow to upgrade held package(s) + #[arg(short = 'S', long, action = ArgAction::SetTrue)] + escape_hold: bool, + /// Skip package integrity check + #[arg(long, action = ArgAction::SetTrue)] + no_hash_check: bool, +} + +pub fn execute(args: Args, session: &Session) -> Result<()> { + let mut queries = args.package.iter().map(|s| s.as_str()).collect::>(); + if queries.is_empty() { + queries.push("*"); + } let mut options = vec![SyncOption::OnlyUpgrade]; - if matches.get_flag("assume-yes") { + if args.assume_yes { options.push(SyncOption::AssumeYes); } - if matches.get_flag("escape-hold") { + if args.escape_hold { options.push(SyncOption::EscapeHold); } - if matches.get_flag("ignore-failure") { + if args.ignore_failure { options.push(SyncOption::IgnoreFailure); } - if matches.get_flag("offline") { + if args.offline { options.push(SyncOption::Offline); } - if matches.get_flag("no-hash-check") { + if args.no_hash_check { options.push(SyncOption::NoHashCheck); } diff --git a/src/lib.rs b/src/lib.rs index effd559..4223cad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ use crossterm::{ use libscoop::Session; use std::{fmt::Display, io}; -mod clap_app; mod cmd; mod cui; mod util; @@ -43,38 +42,12 @@ fn report(err: anyhow::Error) { } } -pub fn create_app(args: Vec) -> bool { +pub fn create_app() -> bool { let _ = create_logger(); - let app = clap_app::build(); let session = Session::default(); let _ = session.set_user_agent("Scoop/1.0 (+http://scoop.sh/) Hok/0.1.0"); - let ret = match app.try_get_matches_from(args) { - Err(e) => { - eprintln!("{}", e); - Ok(()) - } - Ok(matches) => match matches.subcommand() { - Some(("bucket", matches)) => cmd::cmd_bucket(matches, &session), - Some(("cache", matches)) => cmd::cmd_cache(matches, &session), - Some(("cat", matches)) => cmd::cmd_cat(matches, &session), - Some(("cleanup", matches)) => cmd::cmd_cleanup(matches, &session), - Some(("config", matches)) => cmd::cmd_config(matches, &session), - Some(("hold", matches)) => cmd::cmd_hold(matches, &session), - Some(("home", matches)) => cmd::cmd_home(matches, &session), - Some(("info", matches)) => cmd::cmd_info(matches, &session), - Some(("install", matches)) => cmd::cmd_install(matches, &session), - Some(("list", matches)) => cmd::cmd_list(matches, &session), - Some(("search", matches)) => cmd::cmd_search(matches, &session), - Some(("unhold", matches)) => cmd::cmd_unhold(matches, &session), - Some(("uninstall", matches)) => cmd::cmd_uninstall(matches, &session), - Some(("update", matches)) => cmd::cmd_update(matches, &session), - Some(("upgrade", matches)) => cmd::cmd_upgrade(matches, &session), - _ => unimplemented!(), - }, - }; - - match ret { + match cmd::start(&session) { Ok(_) => false, Err(e) => { report(e); diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 427e712..0000000 --- a/src/main.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub fn main() { - let args: Vec = std::env::args().collect(); - std::process::exit(hok::create_app(args) as i32); -}