pub mod install;
pub mod launch;
pub mod list;
pub mod migrate;
pub mod portability;
pub mod profile;
pub mod search;
pub mod toggle;
pub mod uninstall;
pub mod update;
use crate::error::{AppError, AppResult};
use crate::target::Target;
use thunderstore_engine::profile::BatchOutcome;
use thunderstore_engine::profile::modlist::{self, ProfileMod};
pub fn read_modlist(target: &Target) -> AppResult<Vec<ProfileMod>> {
modlist::read(&target.dir).map_err(|error| {
AppError::advice(
format!(
"the install record for {} could not be read.",
crate::target::describe(target)
),
format!(
"{error}\n\nNothing was changed. Every vmm command reads this file \
before doing anything, so each will refuse the same way until it \
parses. Repair {} by hand if you can read what it once held; \
otherwise delete it and reinstall the mods you had, which writes a \
fresh one.",
target.mods_yml().display()
),
&[],
)
})
}
pub fn describe_count(count: usize) -> String {
match count {
1 => "1 mod".to_string(),
n => format!("{n} mods"),
}
}
pub fn report_installed(target: &Target, installed: &[String]) -> AppResult<()> {
let mods = read_modlist(target)?;
for name in installed {
match modlist::find(&mods, name) {
Some(entry) => println!("installed {} {}", entry.name, entry.version_number),
None => println!("installed {name}"),
}
}
Ok(())
}
pub fn report_batch_failures(outcome: &BatchOutcome) -> AppResult<()> {
if outcome.is_ok() {
return Ok(());
}
let named: Vec<String> = outcome
.failed
.iter()
.map(|(name, error)| format!("{name}: {error}"))
.collect();
let tail = match outcome.succeeded.is_empty() {
true => String::new(),
false => format!("\n\n{} succeeded.", describe_count(outcome.succeeded.len())),
};
Err(AppError::advice(
format!(
"{} could not be done.",
describe_count(outcome.failed.len())
),
format!(
"These were left as they were:\n\n {}{tail}",
named.join("\n ")
),
&["vmm list"],
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::Fixture;
#[test]
fn an_installed_name_the_record_does_not_carry_is_still_reported() {
let fixture = Fixture::new();
let target = fixture.target();
report_installed(&target, &["Owner-ModA".to_string()]).unwrap();
}
fn outcome(succeeded: &[&str], failed: &[&str]) -> BatchOutcome {
let mut outcome = BatchOutcome::default();
outcome.succeeded = succeeded.iter().map(|name| name.to_string()).collect();
outcome.failed = failed
.iter()
.map(|name| {
(
name.to_string(),
thunderstore_engine::Error::Profile("no such package".to_string()),
)
})
.collect();
outcome
}
#[test]
fn a_batch_where_nothing_succeeded_does_not_claim_otherwise() {
let message = report_batch_failures(&outcome(&[], &["Owner-Typo"]))
.unwrap_err()
.to_string();
assert!(
!message.contains("succeeded"),
"a batch with no successes must not report any; got: {message}"
);
assert!(message.contains("Owner-Typo"), "got: {message}");
assert!(
message.contains("1 mod could not be done"),
"got: {message}"
);
}
#[test]
fn a_partial_batch_counts_both_sides() {
let message = report_batch_failures(&outcome(&["Owner-ModA", "Owner-ModB"], &["Owner-Typo"]))
.unwrap_err()
.to_string();
assert!(
message.contains("1 mod could not be done"),
"got: {message}"
);
assert!(message.contains("2 mods succeeded"), "got: {message}");
}
#[test]
fn a_batch_with_no_failures_is_not_an_error_at_all() {
assert!(report_batch_failures(&outcome(&["Owner-ModA"], &[])).is_ok());
}
}