diff --git a/rustfmt.toml b/rustfmt.toml index 520073d..ce11fe5 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,6 @@ format_code_in_doc_comments = true -enum_discrim_align_threshold = 20 -struct_field_align_threshold = 20 +# enum_discrim_align_threshold = 20 +# struct_field_align_threshold = 20 condense_wildcard_suffixes = true match_block_trailing_comma = true normalize_doc_attributes = true diff --git a/src/cli.rs b/src/cli.rs index 67376d1..e1e8b6a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -38,7 +38,8 @@ pub enum SubCommands { /// The Modrinth project ID is specified at the bottom of the left sidebar under 'Technical information'. You can also use the project slug in the URL. /// The CurseForge mod ID is specified at the top of the right sidebar under 'About Project'. /// The GitHub identifier is the repository's full name, e.g. `gorilla-devs/ferium`. - identifier: String, + identifiers: Vec, + #[clap(long)] /// The game version will not be checked for this mod dont_check_game_version: bool, @@ -66,6 +67,8 @@ pub enum SubCommands { /// Useful for creating modpack mod lists. /// Complements the verbose flag. markdown: bool, + #[clap(long, short)] + export: bool, }, #[clap(arg_required_else_help = true)] /// Add, configure, delete, switch, list, or upgrade modpacks @@ -87,7 +90,11 @@ pub enum SubCommands { mod_names: Vec, }, /// Download and install the latest version of the mods specified - Upgrade, + Upgrade { + #[clap(long, short)] + /// Don’t print compatible mods + less: bool, + }, } #[derive(Subcommand)] diff --git a/src/main.rs b/src/main.rs index 25a5561..1bd7ba0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,52 +113,47 @@ async fn actual_main(cli_app: Ferium) -> Result<()> { match cli_app.subcommand { SubCommands::Complete { .. } => unreachable!(), SubCommands::Add { - identifier, + identifiers, dont_check_game_version, dont_check_mod_loader, dependencies, } => { let profile = get_active_profile(&mut config)?; check_internet().await?; - if let Ok(project_id) = identifier.parse::() { - subcommands::add::curseforge( + let mut error = false; + for identifier in identifiers { + match subcommands::add::add_mod( + identifier, &curseforge, - project_id, - profile, - Some(!dont_check_game_version), - Some(!dont_check_mod_loader), - dependencies, - ) - .await?; - } else if identifier.split('/').count() == 2 { - let split = identifier.split('/').collect::>(); - subcommands::add::github( - github.repos(split[0], split[1]), + &github, + &modrinth, profile, - Some(!dont_check_game_version), - Some(!dont_check_mod_loader), + dont_check_game_version, + dont_check_mod_loader, + &dependencies, ) - .await?; - } else if let Err(err) = subcommands::add::modrinth( - &modrinth, - &identifier, - profile, - Some(!dont_check_game_version), - Some(!dont_check_mod_loader), - dependencies, - ) - .await - { - return Err( - if err.to_string() == ferinth::Error::NotBase62.to_string() { - anyhow!("Invalid indentifier") - } else { - err + .await + { + Ok(_) => {}, + Err(e) => { + if e.to_string() == libium::add::Error::AlreadyAdded.to_string() { + println!("{} Already added", *TICK); + } else { + eprintln!("{}", e.to_string().red().bold()); + } + error = true; }, - ); + } + } + if error { + return Err(anyhow!("")); } }, - SubCommands::List { verbose, markdown } => { + SubCommands::List { + verbose, + markdown, + export, + } => { let profile = get_active_profile(&mut config)?; check_empty_profile(profile)?; if verbose { @@ -216,10 +211,16 @@ async fn actual_main(cli_app: Ferium) -> Result<()> { } } else { for mod_ in &profile.mods { - println!( - "{:45} {}", - mod_.name.bold(), + if export { match &mod_.identifier { + ModIdentifier::CurseForgeProject(id) => println!("{}", id), + ModIdentifier::ModrinthProject(id) => println!("{}", id), + ModIdentifier::GitHubRepository(name) => { + println!("{}/{}", name.0, name.1) + }, + } + } else { + println!("{:45} {}", mod_.name.bold(), match &mod_.identifier { ModIdentifier::CurseForgeProject(id) => format!("{:10} {}", "CurseForge".red(), id.to_string().dimmed()), ModIdentifier::ModrinthProject(id) => @@ -229,8 +230,8 @@ async fn actual_main(cli_app: Ferium) -> Result<()> { "GitHub".purple(), format!("{}/{}", name.0, name.1).dimmed() ), - }, - ); + },); + } } } }, @@ -355,11 +356,11 @@ async fn actual_main(cli_app: Ferium) -> Result<()> { check_empty_profile(profile)?; subcommands::remove(profile, mod_names)?; }, - SubCommands::Upgrade => { + SubCommands::Upgrade { less } => { check_internet().await?; let profile = get_active_profile(&mut config)?; check_empty_profile(profile)?; - subcommands::upgrade(modrinth, curseforge, github, profile).await?; + subcommands::upgrade(modrinth, curseforge, github, profile, less).await?; }, }; diff --git a/src/subcommands/add.rs b/src/subcommands/add.rs index 803bedf..571e137 100644 --- a/src/subcommands/add.rs +++ b/src/subcommands/add.rs @@ -1,16 +1,66 @@ use crate::{cli::DependencyLevel, CROSS, THEME, TICK}; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use colored::Colorize; use dialoguer::Confirm; -use ferinth::structures::version::DependencyType; -use ferinth::Ferinth; +use ferinth::{structures::version::DependencyType, Ferinth}; use furse::{structures::file_structs::FileRelationType, Furse}; use itertools::Itertools; use libium::{ add, config::structs::{Mod, ModIdentifier, ModLoader, Profile}, }; -use octocrab::repos::RepoHandler; +use octocrab::{repos::RepoHandler, Octocrab}; + +pub async fn add_mod( + identifier: String, + furse: &Furse, + octocrab: &Octocrab, + ferinth: &Ferinth, + profile: &mut Profile, + dont_check_game_version: bool, + dont_check_mod_loader: bool, + dependencies: &Option, +) -> Result<()> { + eprint!("Adding mod {} … ", identifier.bold()); + if let Ok(project_id) = identifier.parse::() { + return curseforge( + &furse, + project_id, + profile, + Some(!dont_check_game_version), + Some(!dont_check_mod_loader), + &dependencies, + ) + .await; + } else if identifier.split('/').count() == 2 { + let split = identifier.split('/').collect::>(); + return github( + octocrab.repos(split[0], split[1]), + profile, + Some(!dont_check_game_version), + Some(!dont_check_mod_loader), + ) + .await; + } else if let Err(err) = modrinth( + &ferinth, + &identifier, + profile, + Some(!dont_check_game_version), + Some(!dont_check_mod_loader), + dependencies, + ) + .await + { + return Err( + if err.to_string() == ferinth::Error::NotBase62.to_string() { + anyhow!("Invalid indentifier") + } else { + err + }, + ); + } + Ok(()) +} #[allow(clippy::expect_used)] pub async fn github( @@ -19,7 +69,6 @@ pub async fn github( should_check_game_version: Option, should_check_mod_loader: Option, ) -> Result<()> { - eprint!("Adding mod... "); let (repo, _) = add::github( &repo_handler, profile, @@ -54,18 +103,21 @@ pub async fn modrinth( profile: &mut Profile, should_check_game_version: Option, should_check_mod_loader: Option, - dependencies: Option, + dependencies: &Option, ) -> Result<()> { - eprint!("Adding mod... "); let project = modrinth.get_project(project_id).await?; - let latest_version = add::modrinth( + let latest_version = match add::modrinth( modrinth, &project, profile, should_check_game_version, should_check_mod_loader, ) - .await?; + .await + { + Ok(latest_version) => latest_version, + Err(e) => return Err(e.into()), + }; println!("{} {}", *TICK, project.title.bold()); profile.mods.push(Mod { name: project.title.trim().into(), @@ -81,7 +133,7 @@ pub async fn modrinth( should_check_mod_loader }, }); - if dependencies != Some(DependencyLevel::None) { + if dependencies != &Some(DependencyLevel::None) { for dependency in &latest_version.dependencies { let mut id = if let Some(project_id) = &dependency.project_id { project_id.clone() @@ -97,7 +149,7 @@ pub async fn modrinth( } if dependency.dependency_type == DependencyType::Required { - eprint!("Adding required dependency {}... ", id.dimmed()); + eprint!(" ⮱ Adding required dependency {}... ", id.dimmed()); let project = modrinth.get_project(&id).await?; match add::modrinth(modrinth, &project, profile, None, None).await { Ok(_) => { @@ -122,17 +174,17 @@ pub async fn modrinth( if matches!(err, add::Error::AlreadyAdded) { println!("{} Already added", *TICK); } else { - bail!(err); + return Err(err.into()); } }, }; } else if dependency.dependency_type == DependencyType::Optional - && (dependencies == Some(DependencyLevel::All) || dependencies.is_none()) + && (dependencies == &Some(DependencyLevel::All) || dependencies.is_none()) { - if dependencies == Some(DependencyLevel::All) { - eprint!("Adding optional dependency {}... ", id.dimmed()); + if dependencies == &Some(DependencyLevel::All) { + eprint!(" ⮱ Adding optional dependency {}... ", id.dimmed()); } else { - eprint!("Checking optional dependency {}... ", id.dimmed()); + eprint!(" ⮱ Checking optional dependency {}... ", id.dimmed()); } let project = modrinth.get_project(&id).await?; match add::modrinth(modrinth, &project, profile, None, None).await { @@ -141,16 +193,20 @@ pub async fn modrinth( println!("{}", *TICK); } // If it's optional, confirm with the user if they want to add it - if dependencies == Some(DependencyLevel::All) - || Confirm::with_theme(&*THEME) + if dependencies == &Some(DependencyLevel::All) + || match Confirm::with_theme(&*THEME) .with_prompt(format!( - "Add optional dependency {} ({})?", + " ⮱ Add optional dependency {} ({})?", project.title.bold(), format!("https://modrinth.com/mod/{}", project.slug) .blue() .underline() )) - .interact()? + .interact() + { + Ok(dependencies) => dependencies, + Err(_) => false, + } { profile.mods.push(Mod { name: project.title.trim().into(), @@ -166,7 +222,7 @@ pub async fn modrinth( should_check_mod_loader }, }); - if dependencies == Some(DependencyLevel::All) { + if dependencies == &Some(DependencyLevel::All) { println!("{} {}", *TICK, project.title.bold()); } } @@ -185,7 +241,7 @@ pub async fn modrinth( if !project.donation_urls.is_empty() { println!( - "Consider supporting the mod creator on {}", + " ⮱ Consider supporting the mod creator on {}", project .donation_urls .iter() @@ -207,9 +263,8 @@ pub async fn curseforge( profile: &mut Profile, should_check_game_version: Option, should_check_mod_loader: Option, - dependencies: Option, + dependencies: &Option, ) -> Result<()> { - eprint!("Adding mod... "); let project = curseforge.get_mod(project_id).await?; let latest_file = add::curseforge( curseforge, @@ -234,11 +289,14 @@ pub async fn curseforge( should_check_mod_loader }, }); - if dependencies != Some(DependencyLevel::None) { + if dependencies != &Some(DependencyLevel::None) { for dependency in &latest_file.dependencies { let id = dependency.mod_id; if dependency.relation_type == FileRelationType::RequiredDependency { - eprint!("Adding required dependency {}... ", id.to_string().dimmed()); + eprint!( + " ⮱ Adding required dependency {}... ", + id.to_string().dimmed() + ); let project = curseforge.get_mod(id).await?; match add::curseforge(curseforge, &project, profile, None, None).await { Ok(_) => { @@ -268,13 +326,16 @@ pub async fn curseforge( }, }; } else if dependency.relation_type == FileRelationType::OptionalDependency - && (dependencies == Some(DependencyLevel::All) || dependencies.is_none()) + && (dependencies == &Some(DependencyLevel::All) || dependencies.is_none()) { - if dependencies == Some(DependencyLevel::All) { - eprint!("Adding optional dependency {}... ", id.to_string().dimmed()); + if dependencies == &Some(DependencyLevel::All) { + eprint!( + " ⮱ Adding optional dependency {}... ", + id.to_string().dimmed() + ); } else { eprint!( - "Checking optional dependency {}... ", + " ⮱ Checking optional dependency {}... ", id.to_string().dimmed() ); } @@ -285,10 +346,10 @@ pub async fn curseforge( println!("{}", *TICK); } // If it's optional, confirm with the user if they want to add it - if dependencies == Some(DependencyLevel::All) + if dependencies == &Some(DependencyLevel::All) || Confirm::with_theme(&*THEME) .with_prompt(format!( - "Add optional dependency {} ({})?", + " ⮱ Add optional dependency {} ({})?", project.name.bold(), project.links.website_url.to_string().blue().underline() )) @@ -308,7 +369,7 @@ pub async fn curseforge( should_check_mod_loader }, }); - if dependencies == Some(DependencyLevel::All) { + if dependencies == &Some(DependencyLevel::All) { println!("{} {}", *TICK, project.name.bold()); } } diff --git a/src/subcommands/upgrade.rs b/src/subcommands/upgrade.rs index b20c6c2..e0525fb 100644 --- a/src/subcommands/upgrade.rs +++ b/src/subcommands/upgrade.rs @@ -28,6 +28,7 @@ pub async fn upgrade( curseforge: Furse, github: Octocrab, profile: &Profile, + less: bool, ) -> Result<()> { let profile = Arc::new(profile.clone()); let to_download = Arc::new(Mutex::new(Vec::new())); @@ -41,7 +42,7 @@ pub async fn upgrade( let modrinth = Arc::new(modrinth); let github = Arc::new(github); - println!("{}\n", "Determining the Latest Compatible Versions".bold()); + println!("{}", "Determining the Latest Compatible Versions".bold()); let semaphore = Arc::new(Semaphore::new(75)); progress_bar .force_lock() @@ -71,17 +72,19 @@ pub async fn upgrade( let progress_bar = progress_bar.force_lock(); match result { Ok((downloadable, backwards_compat)) => { - progress_bar.println(format!( - "{} {:43} {}", - if backwards_compat { - backwards_compat_msg.store(true, Ordering::Relaxed); - YELLOW_TICK.clone() - } else { - TICK.clone() - }, - mod_.name, - downloadable.filename().dimmed() - )); + if !less { + progress_bar.println(format!( + "{} {:43} {}", + if backwards_compat { + backwards_compat_msg.store(true, Ordering::Relaxed); + YELLOW_TICK.clone() + } else { + TICK.clone() + }, + mod_.name, + downloadable.filename().dimmed() + )); + } { let mut to_download = to_download.force_lock(); to_download.push(downloadable);