From c4c6d9365c02cea4ce7b4024e586be8bda688829 Mon Sep 17 00:00:00 2001 From: redruin1 Date: Mon, 15 Sep 2025 16:04:14 -0400 Subject: [PATCH 1/4] Update mod_loader.rs initial implementation --- mod_util/src/mod_loader.rs | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/mod_util/src/mod_loader.rs b/mod_util/src/mod_loader.rs index 9359192..3082a9d 100644 --- a/mod_util/src/mod_loader.rs +++ b/mod_util/src/mod_loader.rs @@ -158,6 +158,11 @@ impl Mod { self.internal.get_file(path) } + // Returns a list of all files/subdirectories that live in `dir`. + pub fn dir_entries(&self, dir: &str) -> Result> { + self.internal.dir_entries(dir) + } + #[must_use] pub const fn wube_mods() -> [&'static str; 5] { ["core", "base", "elevated-rails", "quality", "space-age"] @@ -258,6 +263,44 @@ impl ModType { } } } + + fn dir_entries(&self, dir: &str) -> Result> { + match self { + Self::Folder { path } => { + let path = path.join(dir); + if !path.exists() { + return Err(ModError::PathDoesNotExist(path)); + } + let result = std::fs::read_dir(&path)? + .filter_map(|x| x.ok()) + .filter_map(|x| x.path().to_str().map(|s| s.to_string())) + .map(|x| String::from(x)) + .collect(); + + Ok(result) + } + Self::Zip { + internal_prefix, + zip, + .. + } => { + let path = internal_prefix.clone() + dir; + let mut zip = zip.try_borrow_mut()?; + + if let Err(_) = zip.by_name(&path) { + return Err(ModError::PathDoesNotExist(path.into())); + } + + let result = zip + .file_names() + .filter(|&x| x.starts_with(&path)) + .map(|x| String::from(x)) + .collect(); + + Ok(result) + } + } + } } fn get_zip_internal_folder(path: impl AsRef, zip: &ZipArchive) -> Result { From 42c34f82d6f62beca18820d67d7091fc8b9b2da4 Mon Sep 17 00:00:00 2001 From: redruin1 Date: Fri, 19 Sep 2025 06:11:59 -0400 Subject: [PATCH 2/4] Update mod_loader.rs * changed name to `read_dir` to mimic fs * Now returns a `Vec` structs, too dumb atm to figure out an iterator and the difference should be minimal for normal sized dirs * ModEntry has `is_file()` and `is_dir()` helpers, and most other convenient stuff can be grabbed from `path()` * ModEntry can be easily expanded with additional metadata if desired --- mod_util/src/mod_loader.rs | 76 ++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/mod_util/src/mod_loader.rs b/mod_util/src/mod_loader.rs index 3082a9d..1fd52a0 100644 --- a/mod_util/src/mod_loader.rs +++ b/mod_util/src/mod_loader.rs @@ -6,6 +6,7 @@ use std::{ }; use zip::ZipArchive; +use zip::read::ZipFile; use crate::mod_info::{ModInfo, Version}; @@ -159,8 +160,8 @@ impl Mod { } // Returns a list of all files/subdirectories that live in `dir`. - pub fn dir_entries(&self, dir: &str) -> Result> { - self.internal.dir_entries(dir) + pub fn read_dir(&self, dir: &str) -> Result> { + self.internal.read_dir(dir) } #[must_use] @@ -169,6 +170,50 @@ impl Mod { } } +/// An object which represents a file or folder inside of a Mod, like `fs::DirEntry`. +#[derive(Debug)] +pub struct ModEntry { + path: PathBuf, + is_file: bool, + // Can add all sorts of fancy info as we like +} + +impl ModEntry { + pub fn path(&self) -> &PathBuf { + &self.path + } + pub fn is_file(&self) -> bool { + self.is_file + } + pub fn is_dir(&self) -> bool { + !self.is_file + } + // I don't think we have to worry about symlinks... +} + +impl TryFrom for ModEntry { + type Error = ModError; + + fn try_from(item: std::fs::DirEntry) -> Result { + let metadata = item.metadata()?; + Ok(ModEntry { + path: item.path(), + is_file: metadata.is_file(), + }) + } +} + +impl TryFrom<&ZipFile<'_, File>> for ModEntry { + type Error = ModError; + + fn try_from(item: &ZipFile<'_, File>) -> Result { + Ok(ModEntry { + path: item.name().into(), + is_file: item.is_file(), + }) + } +} + #[derive(Debug)] enum ModType { Folder { @@ -264,20 +309,18 @@ impl ModType { } } - fn dir_entries(&self, dir: &str) -> Result> { + fn read_dir(&self, dir: &str) -> Result> { match self { Self::Folder { path } => { let path = path.join(dir); if !path.exists() { return Err(ModError::PathDoesNotExist(path)); } - let result = std::fs::read_dir(&path)? - .filter_map(|x| x.ok()) - .filter_map(|x| x.path().to_str().map(|s| s.to_string())) - .map(|x| String::from(x)) - .collect(); - Ok(result) + return std::fs::read_dir(&path)? + .filter_map(|x| x.ok()) + .map(|x| ModEntry::try_from(x)) + .collect() } Self::Zip { internal_prefix, @@ -287,17 +330,20 @@ impl ModType { let path = internal_prefix.clone() + dir; let mut zip = zip.try_borrow_mut()?; - if let Err(_) = zip.by_name(&path) { + if let Err(_) = &zip.by_name(&path) { return Err(ModError::PathDoesNotExist(path.into())); } - let result = zip - .file_names() - .filter(|&x| x.starts_with(&path)) - .map(|x| String::from(x)) + // We need to copy all of the `&str`s to `String`s so we don't + // keep the immutable ref to `zip` alive and trigger a double borrow + let entries: Vec = zip.file_names() + .filter(|&x| x.starts_with(&path) && x != path) + .map(|x| x.into()) .collect(); - Ok(result) + return entries.iter() + .map(|x| ModEntry::try_from(&zip.by_name(x)?)) + .collect(); } } } From f24bb19910eb6907f986a35d970c70d6fd14aee3 Mon Sep 17 00:00:00 2001 From: redruin1 Date: Fri, 19 Sep 2025 06:41:38 -0400 Subject: [PATCH 3/4] fmt --- mod_util/src/mod_loader.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mod_util/src/mod_loader.rs b/mod_util/src/mod_loader.rs index 1fd52a0..41060ab 100644 --- a/mod_util/src/mod_loader.rs +++ b/mod_util/src/mod_loader.rs @@ -5,8 +5,8 @@ use std::{ path::{Path, PathBuf}, }; -use zip::ZipArchive; use zip::read::ZipFile; +use zip::ZipArchive; use crate::mod_info::{ModInfo, Version}; @@ -179,7 +179,7 @@ pub struct ModEntry { } impl ModEntry { - pub fn path(&self) -> &PathBuf { + pub fn path(&self) -> &PathBuf { &self.path } pub fn is_file(&self) -> bool { @@ -320,7 +320,7 @@ impl ModType { return std::fs::read_dir(&path)? .filter_map(|x| x.ok()) .map(|x| ModEntry::try_from(x)) - .collect() + .collect(); } Self::Zip { internal_prefix, @@ -334,14 +334,16 @@ impl ModType { return Err(ModError::PathDoesNotExist(path.into())); } - // We need to copy all of the `&str`s to `String`s so we don't + // We need to copy all of the `&str`s to `String`s so we don't // keep the immutable ref to `zip` alive and trigger a double borrow - let entries: Vec = zip.file_names() + let entries: Vec = zip + .file_names() .filter(|&x| x.starts_with(&path) && x != path) .map(|x| x.into()) .collect(); - return entries.iter() + return entries + .iter() .map(|x| ModEntry::try_from(&zip.by_name(x)?)) .collect(); } From c1cef76b1dea7f238278f1715aa3d4396d9d494b Mon Sep 17 00:00:00 2001 From: redruin1 Date: Wed, 24 Sep 2025 07:39:17 -0400 Subject: [PATCH 4/4] iterator rough draft + all the clippy lints on my machine --- mod_util/src/mod_list.rs | 1 + mod_util/src/mod_loader.rs | 46 +++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/mod_util/src/mod_list.rs b/mod_util/src/mod_list.rs index d3c7a70..5093ce8 100644 --- a/mod_util/src/mod_list.rs +++ b/mod_util/src/mod_list.rs @@ -180,6 +180,7 @@ impl ModList { /// /// **Note:** all mods (except `core`) are disabled by default. If you want to mimic the games behaviour you should use [`ModList::load`] after this. #[instrument(name = "generate", skip_all)] + #[allow(clippy::cognitive_complexity)] pub fn generate_custom( read_path: impl AsRef, write_path: impl AsRef, diff --git a/mod_util/src/mod_loader.rs b/mod_util/src/mod_loader.rs index 59ed97d..27d1630 100644 --- a/mod_util/src/mod_loader.rs +++ b/mod_util/src/mod_loader.rs @@ -140,7 +140,7 @@ impl Mod { } // Returns a list of all files/subdirectories that live in `dir`. - pub fn read_dir(&self, dir: &str) -> Result> { + pub fn read_dir(&self, dir: &str) -> Result> + '_>> { self.internal.read_dir(dir) } @@ -159,13 +159,16 @@ pub struct ModEntry { } impl ModEntry { - pub fn path(&self) -> &PathBuf { + #[must_use] + pub const fn path(&self) -> &PathBuf { &self.path } - pub fn is_file(&self) -> bool { + #[must_use] + pub const fn is_file(&self) -> bool { self.is_file } - pub fn is_dir(&self) -> bool { + #[must_use] + pub const fn is_dir(&self) -> bool { !self.is_file } // I don't think we have to worry about symlinks... @@ -174,9 +177,9 @@ impl ModEntry { impl TryFrom for ModEntry { type Error = ModError; - fn try_from(item: std::fs::DirEntry) -> Result { + fn try_from(item: std::fs::DirEntry) -> Result { let metadata = item.metadata()?; - Ok(ModEntry { + Ok(Self { path: item.path(), is_file: metadata.is_file(), }) @@ -186,8 +189,8 @@ impl TryFrom for ModEntry { impl TryFrom<&ZipFile<'_, File>> for ModEntry { type Error = ModError; - fn try_from(item: &ZipFile<'_, File>) -> Result { - Ok(ModEntry { + fn try_from(item: &ZipFile<'_, File>) -> Result { + Ok(Self { path: item.name().into(), is_file: item.is_file(), }) @@ -306,7 +309,7 @@ impl ModType { } } - fn read_dir(&self, dir: &str) -> Result> { + fn read_dir(&self, dir: &str) -> Result> + '_>> { match self { Self::Folder { path } => { let path = path.join(dir); @@ -314,10 +317,11 @@ impl ModType { return Err(ModError::PathDoesNotExist(path)); } - return std::fs::read_dir(&path)? - .filter_map(|x| x.ok()) - .map(|x| ModEntry::try_from(x)) - .collect(); + let result = std::fs::read_dir(&path)? + .filter_map(std::result::Result::ok) + .map(ModEntry::try_from); + + Ok(Box::new(result)) } Self::Zip { internal_prefix, @@ -327,22 +331,22 @@ impl ModType { let path = internal_prefix.clone() + dir; let mut zip = zip.try_borrow_mut()?; - if let Err(_) = &zip.by_name(&path) { + if zip.by_name(&path).is_err() { return Err(ModError::PathDoesNotExist(path.into())); } // We need to copy all of the `&str`s to `String`s so we don't // keep the immutable ref to `zip` alive and trigger a double borrow - let entries: Vec = zip + let entries = zip .file_names() .filter(|&x| x.starts_with(&path) && x != path) - .map(|x| x.into()) - .collect(); + .map(std::convert::Into::into); + + let result = entries.collect::>() + .into_iter() + .map(move |x| ModEntry::try_from(&zip.by_name(&x)?)); - return entries - .iter() - .map(|x| ModEntry::try_from(&zip.by_name(x)?)) - .collect(); + Ok(Box::new(result)) } } }