Compare commits
3 Commits
c8fdf0bc23
...
41e261bb15
| Author | SHA1 | Date | |
|---|---|---|---|
|
41e261bb15
|
|||
|
cb022dd5bf
|
|||
|
2b81393fc9
|
@@ -85,3 +85,135 @@ impl<'a> ConflictSolver<'a> {
|
||||
self.files.iter().map(|e| e.1.to_owned()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_conflict() {
|
||||
let mut solver = ConflictSolver::new();
|
||||
|
||||
let mod1 = InstalledMod::new("mod1", 0);
|
||||
let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin1.esp", 0);
|
||||
|
||||
let mod2 = InstalledMod::new("mod2", 0);
|
||||
let m2f1 = ModFile::new("Data/Plugin2.esp", "Data/Plugin2.esp", 0);
|
||||
|
||||
assert!(solver.add_file(&m1f1, &mod1).is_none());
|
||||
assert!(solver.add_file(&m2f1, &mod2).is_none());
|
||||
|
||||
let export = solver.export_files();
|
||||
assert_eq!(export.len(), 2);
|
||||
assert!(
|
||||
export
|
||||
.iter()
|
||||
.find(|e| e.1 == &mod1 && e.0 == &m1f1)
|
||||
.is_some(),
|
||||
"Missing mod1 file1"
|
||||
);
|
||||
assert!(
|
||||
export
|
||||
.iter()
|
||||
.find(|e| e.1 == &mod2 && e.0 == &m2f1)
|
||||
.is_some(),
|
||||
"Missing mod2 file1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conflict_same_mod_solved() {
|
||||
let mut solver = ConflictSolver::new();
|
||||
|
||||
let mod1 = InstalledMod::new("mod1", 0);
|
||||
let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin.esp", 0);
|
||||
let m1f2 = ModFile::new("Data/Plugin_alt.esp", "Data/Plugin.esp", 1);
|
||||
|
||||
assert!(solver.add_file(&m1f1, &mod1).is_none());
|
||||
assert!(solver.add_file(&m1f2, &mod1).is_none());
|
||||
|
||||
let export = solver.export_files();
|
||||
assert_eq!(export.len(), 1);
|
||||
assert!(
|
||||
export
|
||||
.iter()
|
||||
.find(|e| e.1 == &mod1 && e.0 == &m1f2)
|
||||
.is_some(),
|
||||
"Missing mod1 file2"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conflict_diff_mod_solved() {
|
||||
let mut solver = ConflictSolver::new();
|
||||
|
||||
let mod1 = InstalledMod::new("mod1", 0);
|
||||
let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin.esp", 0);
|
||||
|
||||
let mod2 = InstalledMod::new("mod2", 1);
|
||||
let m2f1 = ModFile::new("Data/Plugin.esp", "Data/Plugin.esp", 0);
|
||||
|
||||
assert!(solver.add_file(&m1f1, &mod1).is_none());
|
||||
assert!(solver.add_file(&m2f1, &mod2).is_none());
|
||||
|
||||
let export = solver.export_files();
|
||||
assert_eq!(export.len(), 1);
|
||||
assert!(
|
||||
export
|
||||
.iter()
|
||||
.find(|e| e.1 == &mod2 && e.0 == &m2f1)
|
||||
.is_some(),
|
||||
"Missing mod2 file1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conflict_same_mod() {
|
||||
let mut solver = ConflictSolver::new();
|
||||
|
||||
let mod1 = InstalledMod::new("mod1", 0);
|
||||
let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin.esp", 0);
|
||||
let m1f2 = ModFile::new("Data/Plugin_alt.esp", "Data/Plugin.esp", 0);
|
||||
|
||||
assert!(solver.add_file(&m1f1, &mod1).is_none());
|
||||
let conflict = solver.add_file(&m1f2, &mod1);
|
||||
|
||||
assert!(conflict.is_some());
|
||||
let unwraped = conflict.expect("Aserted before");
|
||||
|
||||
assert!(
|
||||
unwraped.rhs_mod == unwraped.lhs_mod,
|
||||
"Not same mod in conflict"
|
||||
);
|
||||
assert!(unwraped.rhs_file != unwraped.lhs_file, "Files are the same");
|
||||
assert!(
|
||||
unwraped.rhs_file == &m1f1 || unwraped.rhs_file == &m1f2,
|
||||
"One file not found in conflict "
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conflict_diff_mod() {
|
||||
let mut solver = ConflictSolver::new();
|
||||
|
||||
let mod1 = InstalledMod::new("mod1", 0);
|
||||
let m1f1 = ModFile::new("Data/Plugin1.esp", "Data/Plugin.esp", 0);
|
||||
|
||||
let mod2 = InstalledMod::new("mod2", 0);
|
||||
let m2f1 = ModFile::new("Data/Plugin.esp", "Data/Plugin.esp", 0);
|
||||
|
||||
assert!(solver.add_file(&m1f1, &mod1).is_none());
|
||||
let conflict = solver.add_file(&m2f1, &mod2);
|
||||
|
||||
assert!(conflict.is_some());
|
||||
let unwraped = conflict.expect("Aserted before");
|
||||
|
||||
assert!(unwraped.rhs_mod != unwraped.lhs_mod, "Same mod in conflict");
|
||||
assert!(unwraped.rhs_file != unwraped.lhs_file, "Files are the same");
|
||||
assert!(
|
||||
unwraped.rhs_file == &m1f1 || unwraped.lhs_file == &m1f1,
|
||||
"One file not found in conflict "
|
||||
);
|
||||
assert_eq!(unwraped.rhs_file.dst(), "Data/Plugin.esp");
|
||||
}
|
||||
}
|
||||
|
||||
12
src/lib.rs
Normal file
12
src/lib.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
pub mod activator;
|
||||
pub mod cli;
|
||||
pub mod file_conflict_solver;
|
||||
pub mod fomod;
|
||||
pub mod install_prompt;
|
||||
pub mod instance;
|
||||
pub mod load_order;
|
||||
pub mod mod_config_installer;
|
||||
pub mod nexus;
|
||||
pub mod types;
|
||||
pub mod unpacker;
|
||||
pub mod utils;
|
||||
20
src/main.rs
20
src/main.rs
@@ -3,28 +3,16 @@ use clap::Parser;
|
||||
use log::{debug, error, info};
|
||||
use std::{error::Error, path::Path};
|
||||
|
||||
use crate::{
|
||||
use fomod_manager::{
|
||||
activator::activate_instance,
|
||||
cli::Args,
|
||||
instance::{files_to_install_mod, insert_mod_to_instance},
|
||||
cli::{self, Args},
|
||||
instance::{self, files_to_install_mod, insert_mod_to_instance},
|
||||
load_order,
|
||||
nexus::{NexusAPI, download_nxm},
|
||||
types::RootConfig,
|
||||
unpacker::unpack,
|
||||
};
|
||||
|
||||
mod activator;
|
||||
mod cli;
|
||||
mod file_conflict_solver;
|
||||
mod fomod;
|
||||
mod install_prompt;
|
||||
mod instance;
|
||||
mod load_order;
|
||||
mod mod_config_installer;
|
||||
mod nexus;
|
||||
mod types;
|
||||
mod unpacker;
|
||||
mod utils;
|
||||
|
||||
fn command_activate(
|
||||
root_config: &RootConfig,
|
||||
instance_id: &str,
|
||||
|
||||
@@ -14,6 +14,12 @@ pub struct Game {
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn new(path: impl AsRef<Path>) -> Self {
|
||||
Self {
|
||||
path: path.as_ref().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export_links(&self) -> Result<Vec<Link>, io::Error> {
|
||||
let links: Vec<Link> = walk_all_files(&self.path)?
|
||||
.map(|entry| {
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
utils::walk_all_files,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||
pub struct ModFile {
|
||||
/// Relative path in the mod
|
||||
src: PathBuf,
|
||||
|
||||
@@ -29,6 +29,22 @@ pub struct ModdedInstance {
|
||||
}
|
||||
|
||||
impl ModdedInstance {
|
||||
pub fn new(
|
||||
game: &str,
|
||||
mods: &[InstalledMod],
|
||||
load_order: &[String],
|
||||
overrides: &[Link],
|
||||
self_path: impl AsRef<Path>,
|
||||
) -> Self {
|
||||
Self {
|
||||
game: game.to_owned(),
|
||||
mods: mods.to_owned(),
|
||||
load_order: load_order.to_owned(),
|
||||
game_file_overrides: overrides.to_owned(),
|
||||
self_path: self_path.as_ref().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_from_file(path: impl AsRef<Path>) -> Result<Self, ConfigReadWriteError> {
|
||||
debug!(
|
||||
"Loading ModdedInstance from file: {}",
|
||||
@@ -94,3 +110,52 @@ impl ModdedInstance {
|
||||
self.mods.iter().flat_map(|e| e.active_plugins())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn create_config() -> ModdedInstance {
|
||||
ModdedInstance::new(
|
||||
"sse",
|
||||
&[InstalledMod::new("mod1", 0), InstalledMod::new("mod1", 0)],
|
||||
&["Plugin1.esp".to_owned(), "Plugin2.esp".to_owned()],
|
||||
&[Link::new("file1.txt", "file2.txt")],
|
||||
"/config/instance.toml",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_members() {
|
||||
let cfg = create_config();
|
||||
|
||||
assert_eq!(cfg.game_file_overrides().len(), 1);
|
||||
assert_eq!(cfg.mods().len(), 2);
|
||||
assert_eq!(cfg.load_order().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_mod() {
|
||||
let mut cfg = create_config();
|
||||
|
||||
let new_mod = InstalledMod::new("mod3", 1);
|
||||
|
||||
cfg.update_or_create_mod(&new_mod);
|
||||
|
||||
let mods = cfg.mods();
|
||||
assert_eq!(mods.len(), 3);
|
||||
|
||||
let found_mod = mods.iter().find(|e| e.mod_id() == "mod3");
|
||||
|
||||
assert!(found_mod.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_mod() {
|
||||
let mut cfg = create_config();
|
||||
let new_mod = InstalledMod::new("mod1", 1);
|
||||
|
||||
cfg.update_or_create_mod(&new_mod);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,25 +52,6 @@ impl RootConfig {
|
||||
.to_owned();
|
||||
config.self_path = absolute;
|
||||
|
||||
if config.mod_location.is_relative() {
|
||||
config.mod_location = config.self_parent.join(config.mod_location).to_owned();
|
||||
debug!(
|
||||
"Resolved mod_location to absolue path: {}",
|
||||
config.mod_location.to_string_lossy()
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(dl_location) = &config.download_location
|
||||
&& dl_location.is_relative()
|
||||
{
|
||||
let dl_abs_path = config.self_parent.join(dl_location).to_owned();
|
||||
debug!(
|
||||
"Resolve download_location to absolute path {}",
|
||||
dl_abs_path.to_string_lossy()
|
||||
);
|
||||
config.download_location = Some(dl_abs_path);
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -106,16 +87,26 @@ impl RootConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mod_location(&self) -> &Path {
|
||||
&self.mod_location
|
||||
pub fn mod_location(&self) -> PathBuf {
|
||||
if self.mod_location.is_relative() {
|
||||
self.self_parent.join(&self.mod_location)
|
||||
} else {
|
||||
self.mod_location.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nexus_api_key(&self) -> Option<&str> {
|
||||
self.nexus_api_key.as_deref()
|
||||
}
|
||||
|
||||
pub fn download_location(&self) -> Option<&Path> {
|
||||
self.download_location.as_deref()
|
||||
pub fn download_location(&self) -> Option<PathBuf> {
|
||||
self.download_location.as_ref().map(|e| {
|
||||
if e.is_relative() {
|
||||
self.self_parent.join(e)
|
||||
} else {
|
||||
e.clone()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,3 +114,118 @@ impl RootConfig {
|
||||
struct InstancePointer {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn create_config() -> RootConfig {
|
||||
RootConfig {
|
||||
games: HashMap::from([("sse".to_owned(), Game::new("/games/sse"))]),
|
||||
mod_location: PathBuf::from("mods"),
|
||||
download_location: Some(PathBuf::from("download")),
|
||||
nexus_api_key: Some("1234".to_owned()),
|
||||
instances: HashMap::from([(
|
||||
"instance1".to_owned(),
|
||||
InstancePointer {
|
||||
path: PathBuf::from("instances/instance1.toml"),
|
||||
},
|
||||
)]),
|
||||
mods: HashMap::from([(
|
||||
"mod1".to_owned(),
|
||||
ModConfig::new("mod1", PathBuf::from("mod1")),
|
||||
)]),
|
||||
self_path: PathBuf::from("/config/root.toml"),
|
||||
self_parent: PathBuf::from("/config"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_game() {
|
||||
let cfg = create_config();
|
||||
|
||||
let game = cfg.game_by_id("sse");
|
||||
|
||||
assert!(game.is_some());
|
||||
|
||||
let unwraped = game.expect("Asserted before");
|
||||
assert_eq!(unwraped.install_location(), "/games/sse");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_missing_game() {
|
||||
let cfg = create_config();
|
||||
|
||||
let game = cfg.game_by_id("starfield");
|
||||
|
||||
assert!(game.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_mod() {
|
||||
let cfg = create_config();
|
||||
|
||||
let found_mod = cfg.mod_by_id("mod1");
|
||||
|
||||
assert!(found_mod.is_some());
|
||||
|
||||
let unwraped = found_mod.expect("Asserted before");
|
||||
assert_eq!(unwraped.path(), "mod1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_missing_mod() {
|
||||
let cfg = create_config();
|
||||
|
||||
let found_mod = cfg.mod_by_id("mod200");
|
||||
|
||||
assert!(found_mod.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_api_key() {
|
||||
let cfg = create_config();
|
||||
|
||||
assert_eq!(cfg.nexus_api_key(), Some("1234"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_download_location() {
|
||||
let cfg = create_config();
|
||||
|
||||
let dl = cfg.download_location();
|
||||
|
||||
assert!(dl.is_some());
|
||||
|
||||
let unwraped = dl.expect("Asserted before");
|
||||
|
||||
assert!(unwraped.is_absolute(), "Path not absolute");
|
||||
assert_eq!(unwraped, PathBuf::from("/config/download"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_mod_location() {
|
||||
let cfg = create_config();
|
||||
|
||||
let mod_dir = cfg.mod_location();
|
||||
|
||||
assert!(mod_dir.is_absolute(), "Path not absolute");
|
||||
assert_eq!(mod_dir, PathBuf::from("/config/mods"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_mod() {
|
||||
let mut cfg = create_config();
|
||||
|
||||
let new_mod = ModConfig::new("new_mod", "new_mod_path");
|
||||
|
||||
cfg.add_mod(&new_mod);
|
||||
|
||||
let found_mod = cfg.mod_by_id("new_mod");
|
||||
|
||||
assert!(found_mod.is_some());
|
||||
let unwraped = found_mod.expect("Asserted before");
|
||||
|
||||
assert_eq!(unwraped.path(), new_mod.path());
|
||||
}
|
||||
}
|
||||
|
||||
21
tests/data/root_config_complex.toml
Normal file
21
tests/data/root_config_complex.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
mod_location = "mods"
|
||||
download_location = "downloads"
|
||||
nexus_api_key = "1234"
|
||||
|
||||
[games.sse]
|
||||
path = "/home/user/games/sse"
|
||||
|
||||
[instances.example1]
|
||||
path = "example1.toml"
|
||||
|
||||
[instances.example2]
|
||||
path = "/home/user/example2.toml"
|
||||
|
||||
[mods.mod1]
|
||||
path = "/home/user/mods/mod1"
|
||||
|
||||
[mods."mod2"]
|
||||
path = "mod2"
|
||||
|
||||
[mods.mod3]
|
||||
path = "mods3"
|
||||
1
tests/data/root_config_minimal.toml
Normal file
1
tests/data/root_config_minimal.toml
Normal file
@@ -0,0 +1 @@
|
||||
mod_location = "mods"
|
||||
51
tests/root_config_test.rs
Normal file
51
tests/root_config_test.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use fomod_manager::types::RootConfig;
|
||||
|
||||
fn get_parent() -> PathBuf {
|
||||
PathBuf::from(file!()).parent().unwrap().to_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_minimal() {
|
||||
let config =
|
||||
RootConfig::load_from_file(get_parent().join("data/root_config_minimal.toml")).unwrap();
|
||||
|
||||
assert!(config.mod_location().ends_with("mods"));
|
||||
assert!(
|
||||
config.download_location().is_none(),
|
||||
"Download location should be None"
|
||||
);
|
||||
assert!(config.nexus_api_key().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_complex() {
|
||||
let config =
|
||||
RootConfig::load_from_file(get_parent().join("data/root_config_complex.toml")).unwrap();
|
||||
|
||||
assert!(config.mod_location().ends_with("mods"));
|
||||
assert!(
|
||||
config
|
||||
.download_location()
|
||||
.is_some_and(|e| e.ends_with("downloads"))
|
||||
);
|
||||
assert_eq!(config.nexus_api_key(), Some("1234"));
|
||||
|
||||
assert!(
|
||||
config
|
||||
.game_by_id("sse")
|
||||
.is_some_and(|e| e.install_location() == "/home/user/games/sse"),
|
||||
"Installed game wrong path"
|
||||
);
|
||||
|
||||
assert!(config.game_by_id("starfield").is_none());
|
||||
|
||||
assert!(config.mod_by_id("mod1").is_some());
|
||||
assert!(config.mod_by_id("mod100").is_none());
|
||||
assert!(
|
||||
config
|
||||
.mod_by_id("mod1")
|
||||
.is_some_and(|e| e.path().ends_with("mods/mod1"))
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user