diff --git a/src/archive/ba2.rs b/src/archive/ba2.rs index b8ea5d19..23cf8480 100644 --- a/src/archive/ba2.rs +++ b/src/archive/ba2.rs @@ -77,9 +77,10 @@ pub(super) fn read_assets( let file_path_bytes = trim_slashes(&file_path_bytes); - let (folder_hash, file_hash) = rsplit_on(file_path_bytes, b'\\') - .map(|(folder_path, file_path)| (hash(&folder_path), hash(&file_path))) - .unwrap_or_else(|| (0, hash(&file_path_bytes))); + let (folder_hash, file_hash) = rsplit_on(file_path_bytes, b'\\').map_or_else( + || (0, hash(&file_path_bytes)), + |(folder_path, file_path)| (hash(&folder_path), hash(&file_path)), + ); let file_hashes: &mut BTreeSet = assets.entry(folder_hash).or_default(); diff --git a/src/archive/bsa.rs b/src/archive/bsa.rs index b9f84020..60d328ee 100644 --- a/src/archive/bsa.rs +++ b/src/archive/bsa.rs @@ -173,13 +173,10 @@ fn read_assets_with_header( } }; - let file_records_buffer = match file_records_buffer.get(file_records_offset..) { - Some(s) => s, - None => { - return Err(ArchiveParsingError::InvalidFileRecordsOffset( - file_records_offset, - )); - } + let Some(file_records_buffer) = file_records_buffer.get(file_records_offset..) else { + return Err(ArchiveParsingError::InvalidFileRecordsOffset( + file_records_offset, + )); }; let file_hashes: &mut BTreeSet = entry.or_default(); diff --git a/src/archive/error.rs b/src/archive/error.rs index 8dafe31b..379f6628 100644 --- a/src/archive/error.rs +++ b/src/archive/error.rs @@ -53,27 +53,26 @@ impl std::fmt::Display for ArchiveParsingError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::IoError(_) => write!(f, "an I/O error occurred"), - Self::UnsupportedHeaderVersion(v) => write!(f, "unsupported archive version {}", v), - Self::UnsupportedHeaderArchiveType(a) => write!(f, "unsupported archive type {:?}", a), - Self::UnsupportedArchiveTypeId(t) => write!(f, "unsupported archive type ID {:?}", t), - Self::InvalidRecordsOffset(o) => write!(f, "invalid records offset {}", o), + Self::UnsupportedHeaderVersion(v) => write!(f, "unsupported archive version {v}"), + Self::UnsupportedHeaderArchiveType(a) => write!(f, "unsupported archive type {a:?}"), + Self::UnsupportedArchiveTypeId(t) => write!(f, "unsupported archive type ID {t:?}"), + Self::InvalidRecordsOffset(o) => write!(f, "invalid records offset {o}"), Self::InvalidFolderNameLengthOffset(o) => { - write!(f, "invalid folder name length offset {}", o) + write!(f, "invalid folder name length offset {o}") } - Self::InvalidFileRecordsOffset(o) => write!(f, "invalid file records offset {}", o), + Self::InvalidFileRecordsOffset(o) => write!(f, "invalid file records offset {o}"), Self::UsesBigEndianNumbers => { write!(f, "archive uses big-endian numbers, which is unsupported") } Self::FolderHashCollision(h) => { - write!(f, "unexpected collision for folder name hash {:x}", h) + write!(f, "unexpected collision for folder name hash {h:x}") } Self::HashCollision { folder_hash, file_hash, } => write!( f, - "unexpected collision for file name hash {:x} in set for folder name hash {:x}", - file_hash, folder_hash + "unexpected collision for file name hash {file_hash:x} in set for folder name hash {folder_hash:x}" ), } } diff --git a/src/archive/find.rs b/src/archive/find.rs index a6e87061..93f5785c 100644 --- a/src/archive/find.rs +++ b/src/archive/find.rs @@ -62,9 +62,8 @@ fn find_associated_archives_with_suffixes( archive_extension: &str, supported_suffixes: &[&str], ) -> Vec { - let file_stem = match plugin_path.file_stem() { - Some(s) => s, - None => return Vec::new(), + let Some(file_stem) = plugin_path.file_stem() else { + return Vec::new(); }; supported_suffixes @@ -89,9 +88,8 @@ fn find_associated_archives_with_arbitrary_suffixes( Some(s) => s.len(), None => return Vec::new(), }; - let plugin_extension = match plugin_path.extension() { - Some(e) => e, - None => return Vec::new(), + let Some(plugin_extension) = plugin_path.extension() else { + return Vec::new(); }; game_cache @@ -101,9 +99,8 @@ fn find_associated_archives_with_arbitrary_suffixes( // but case insensitively. This is hard to do accurately, so // instead check if the plugin with the same length basename and // and the given plugin's file extension is equivalent. - let archive_filename = match path.file_name().and_then(|s| s.to_str()) { - Some(f) => f, - None => return false, + let Some(archive_filename) = path.file_name().and_then(|s| s.to_str()) else { + return false; }; // Can't just slice the archive filename to the same length as the plugin file stem directly because that might not slice on a character boundary, so truncate the byte slice and then check it's still valid UTF-8. @@ -111,11 +108,10 @@ fn find_associated_archives_with_arbitrary_suffixes( return false; } - let filename = - match std::str::from_utf8(&archive_filename.as_bytes()[..plugin_stem_len]) { - Ok(f) => f, - Err(_) => return false, - }; + let Ok(filename) = std::str::from_utf8(&archive_filename.as_bytes()[..plugin_stem_len]) + else { + return false; + }; let archive_plugin_path = plugin_path .with_file_name(filename) @@ -129,10 +125,6 @@ fn find_associated_archives_with_arbitrary_suffixes( #[cfg(windows)] fn are_file_paths_equivalent(lhs: &Path, rhs: &Path) -> bool { - if lhs == rhs { - return true; - } - use std::fs::File; use std::os::windows::io::AsRawHandle; use windows::Win32::{ @@ -140,14 +132,16 @@ fn are_file_paths_equivalent(lhs: &Path, rhs: &Path) -> bool { Storage::FileSystem::{BY_HANDLE_FILE_INFORMATION, GetFileInformationByHandle}, }; - let lhs_file = match File::open(lhs) { - Ok(f) => f, - Err(_) => return false, + if lhs == rhs { + return true; + } + + let Ok(lhs_file) = File::open(lhs) else { + return false; }; - let rhs_file = match File::open(rhs) { - Ok(f) => f, - Err(_) => return false, + let Ok(rhs_file) = File::open(rhs) else { + return false; }; let mut lhs_info = BY_HANDLE_FILE_INFORMATION::default(); @@ -170,20 +164,18 @@ fn are_file_paths_equivalent(lhs: &Path, rhs: &Path) -> bool { #[cfg(not(windows))] fn are_file_paths_equivalent(lhs: &Path, rhs: &Path) -> bool { + use std::os::unix::fs::MetadataExt; + if lhs == rhs { return true; } - use std::os::unix::fs::MetadataExt; - - let lhs_metadata = match lhs.metadata() { - Ok(m) => m, - _ => return false, + let Ok(lhs_metadata) = lhs.metadata() else { + return false; }; - let rhs_metadata = match rhs.metadata() { - Ok(m) => m, - _ => return false, + let Ok(rhs_metadata) = rhs.metadata() else { + return false; }; lhs_metadata.dev() == rhs_metadata.dev() && lhs_metadata.ino() == rhs_metadata.ino() diff --git a/src/archive/mod.rs b/src/archive/mod.rs index 950fbeb9..936a3ae9 100644 --- a/src/archive/mod.rs +++ b/src/archive/mod.rs @@ -61,9 +61,9 @@ mod tests { let path = PathBuf::from("./testing-plugins/Skyrim/Data/Blank.bsa"); let assets2 = assets_in_archives(&[path]); - assert_eq!(assets1.get(&0), assets2.get(&0x2E01002E)); + assert_eq!(assets1.get(&0), assets2.get(&0x2E01_002E)); - assert!(!do_assets_overlap(&assets1, &assets2)) + assert!(!do_assets_overlap(&assets1, &assets2)); } } } diff --git a/src/archive/parse.rs b/src/archive/parse.rs index 6487b2dc..c89f0bd1 100644 --- a/src/archive/parse.rs +++ b/src/archive/parse.rs @@ -141,7 +141,7 @@ mod tests { let path = Path::new("./testing-plugins/Oblivion/Data/Blank.bsa"); let assets = get_assets_in_archive(path).unwrap(); - let files_count: usize = assets.values().map(|v| v.len()).sum(); + let files_count: usize = assets.values().map(BTreeSet::len).sum(); let expected_key = 0; assert_eq!(1, assets.len()); @@ -149,7 +149,7 @@ mod tests { assert_eq!(expected_key, *assets.first_key_value().unwrap().0); assert_eq!(1, assets.get(&expected_key).unwrap().len()); assert_eq!( - 0x4670B6836C077365, + 0x4670_B683_6C07_7365, *assets.get(&expected_key).unwrap().first().unwrap() ); } @@ -159,15 +159,15 @@ mod tests { let path = Path::new("./testing-plugins/Skyrim/Data/Blank.bsa"); let assets = get_assets_in_archive(path).unwrap(); - let files_count: usize = assets.values().map(|v| v.len()).sum(); + let files_count: usize = assets.values().map(BTreeSet::len).sum(); - let expected_key = 0x2E01002E; + let expected_key = 0x2E01_002E; assert_eq!(1, assets.len()); assert_eq!(1, files_count); assert_eq!(expected_key, *assets.first_key_value().unwrap().0); assert_eq!(1, assets.get(&expected_key).unwrap().len()); assert_eq!( - 0x4670B6836C077365, + 0x4670_B683_6C07_7365, *assets.get(&expected_key).unwrap().first().unwrap() ); } @@ -177,15 +177,15 @@ mod tests { let path = Path::new("./testing-plugins/SkyrimSE/Data/Blank.bsa"); let assets = get_assets_in_archive(path).unwrap(); - let files_count: usize = assets.values().map(|v| v.len()).sum(); + let files_count: usize = assets.values().map(BTreeSet::len).sum(); - let expected_key = 0xB68102C964176E73; + let expected_key = 0xB681_02C9_6417_6E73; assert_eq!(1, assets.len()); assert_eq!(1, files_count); assert_eq!(expected_key, *assets.first_key_value().unwrap().0); assert_eq!(1, assets.get(&expected_key).unwrap().len()); assert_eq!( - 0x4670B6836C077365, + 0x4670_B683_6C07_7365, *assets.get(&expected_key).unwrap().first().unwrap() ); } @@ -195,7 +195,7 @@ mod tests { let path = Path::new("./testing-plugins/Fallout 4/Data/Blank - Main.ba2"); let assets = get_assets_in_archive(path).unwrap(); - let files_count: usize = assets.values().map(|v| v.len()).sum(); + let files_count: usize = assets.values().map(BTreeSet::len).sum(); let expected_key = hash("dev\\git\\testing-plugins".as_bytes()); let expected_file_hash = hash("license.txt".as_bytes()); @@ -214,7 +214,7 @@ mod tests { let path = Path::new("./testing-plugins/Fallout 4/Data/Blank - Textures.ba2"); let assets = get_assets_in_archive(path).unwrap(); - let files_count: usize = assets.values().map(|v| v.len()).sum(); + let files_count: usize = assets.values().map(BTreeSet::len).sum(); let expected_key = hash("dev\\git\\testing-plugins".as_bytes()); let expected_file_hash = hash("blank.dds".as_bytes()); @@ -260,15 +260,15 @@ mod tests { let assets = assets_in_archives(&paths); - let files_count: usize = assets.values().map(|v| v.len()).sum(); + let files_count: usize = assets.values().map(BTreeSet::len).sum(); assert_eq!(1, assets.len()); assert_eq!(1, files_count); let (key, value) = assets.first_key_value().unwrap(); - assert_eq!(0x2E01002E, *key); + assert_eq!(0x2E01_002E, *key); assert_eq!(1, value.len()); - assert_eq!(0x4670B6836C077365, *value.first().unwrap()); + assert_eq!(0x4670_B683_6C07_7365, *value.first().unwrap()); } #[test] @@ -281,22 +281,22 @@ mod tests { let assets = assets_in_archives(&paths); - let files_count: usize = assets.values().map(|v| v.len()).sum(); + let files_count: usize = assets.values().map(BTreeSet::len).sum(); assert_eq!(3, assets.len()); assert_eq!(3, files_count); let value = assets.get(&0).unwrap(); assert_eq!(1, value.len()); - assert_eq!(0x4670B6836C077365, *value.first().unwrap()); + assert_eq!(0x4670_B683_6C07_7365, *value.first().unwrap()); - let value = assets.get(&0x2E01002E).unwrap(); + let value = assets.get(&0x2E01_002E).unwrap(); assert_eq!(1, value.len()); - assert_eq!(0x4670B6836C077365, *value.first().unwrap()); + assert_eq!(0x4670_B683_6C07_7365, *value.first().unwrap()); - let value = assets.get(&0xB68102C964176E73).unwrap(); + let value = assets.get(&0xB681_02C9_6417_6E73).unwrap(); assert_eq!(1, value.len()); - assert_eq!(0x4670B6836C077365, *value.first().unwrap()); + assert_eq!(0x4670_B683_6C07_7365, *value.first().unwrap()); } } } diff --git a/src/database/conditions.rs b/src/database/conditions.rs index d30a3e5a..3fd83081 100644 --- a/src/database/conditions.rs +++ b/src/database/conditions.rs @@ -132,9 +132,10 @@ mod tests { plugin.set_group("group1".into()); let condition = "file(\"missing.esp\")".to_string(); - let file1 = File::new(BLANK_ESP.into()); - let file2 = File::new(BLANK_DIFFERENT_ESM.into()).with_condition(condition.clone()); - let files = vec![file1.clone(), file2]; + let files = vec![ + File::new(BLANK_ESP.into()), + File::new(BLANK_DIFFERENT_ESM.into()).with_condition(condition.clone()), + ]; plugin.set_load_after_files(files.clone()); plugin.set_requirements(files.clone()); plugin.set_incompatibilities(files.clone()); @@ -149,8 +150,8 @@ mod tests { Tag::new("Relev".into(), TagSuggestion::Addition).with_condition(condition.clone()); plugin.set_tags(vec![tag1.clone(), tag2]); - let info1 = PluginCleaningData::new(0x374E2A6F, "utility1".into()); - let info2 = PluginCleaningData::new(0xDEADBEEF, "utility2".into()); + let info1 = PluginCleaningData::new(0x374E_2A6F, "utility1".into()); + let info2 = PluginCleaningData::new(0xDEAD_BEEF, "utility2".into()); plugin.set_dirty_info(vec![info1.clone(), info2.clone()]); plugin.set_clean_info(vec![info1.clone(), info2.clone()]); @@ -160,7 +161,7 @@ mod tests { ); let result = evaluate_all_conditions(plugin, &state).unwrap().unwrap(); - let expected_files = &[file1]; + let expected_files = &[files[0].clone()]; let expected_info = &[info1]; assert_eq!("group1", result.group().unwrap()); assert_eq!(expected_files, result.load_after_files()); diff --git a/src/database/mod.rs b/src/database/mod.rs index f08f78f2..8897b772 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -116,8 +116,11 @@ impl Database { let mut doc = MetadataDocument::default(); for plugin in self.masterlist.plugins_iter() { - let mut minimal_plugin = PluginMetadata::new(plugin.name()) - .expect("Regex plugin name from existing PluginMetadata object is valid"); + let Ok(mut minimal_plugin) = PluginMetadata::new(plugin.name()) else { + // This should never happen because the regex plugin name from + // an existing PluginMetadata object should be valid. + continue; + }; minimal_plugin.set_tags(plugin.tags().to_vec()); minimal_plugin.set_dirty_info(plugin.dirty_info().to_vec()); @@ -300,7 +303,7 @@ impl Database { } fn validate_write_path(output_path: &Path, mode: WriteMode) -> Result<(), WriteMetadataError> { - if !output_path.parent().map(|p| p.exists()).unwrap_or(false) { + if !output_path.parent().is_some_and(Path::exists) { Err(WriteMetadataError::new( output_path.into(), WriteMetadataErrorReason::ParentDirectoryNotFound, @@ -975,7 +978,7 @@ plugins: .plugin_metadata(BLANK_ESM, true, false) .unwrap() .is_none() - ) + ); } #[test] @@ -990,7 +993,7 @@ plugins: .plugin_metadata(BLANK_ESM, true, false) .unwrap() .is_none() - ) + ); } #[test] @@ -1079,7 +1082,7 @@ plugins: .plugin_user_metadata(BLANK_ESM, false) .unwrap() .is_none() - ) + ); } #[test] @@ -1094,7 +1097,7 @@ plugins: .plugin_user_metadata(BLANK_ESM, false) .unwrap() .is_none() - ) + ); } #[test] diff --git a/src/error.rs b/src/error.rs index fc9a4a71..28372120 100644 --- a/src/error.rs +++ b/src/error.rs @@ -209,10 +209,10 @@ impl std::fmt::Display for SortPluginsError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::DatabaseLockPoisoned => DatabaseLockPoisonError.fmt(f), - Self::PluginNotLoaded(n) => write!(f, "the plugin \"{}\" has not been loaded", n), - Self::UndefinedGroup(g) => write!(f, "the group \"{}\" does not exist", g), + Self::PluginNotLoaded(n) => write!(f, "the plugin \"{n}\" has not been loaded"), + Self::UndefinedGroup(g) => write!(f, "the group \"{g}\" does not exist"), Self::CycleFound(c) => write!(f, "found a cycle: {}", display_cycle(c)), - Self::CycleFoundInvolving(n) => write!(f, "found a cycle involving \"{}\"", n), + Self::CycleFoundInvolving(n) => write!(f, "found a cycle involving \"{n}\""), Self::PluginDataError(_) => write!(f, "failed to read loaded plugin data"), Self::MetadataRetrievalError(_) => write!(f, "failed to retrieve plugin metadata"), Self::PathfindingError(_) => write!(f, "failed to find a path in the plugins graph"), diff --git a/src/game.rs b/src/game.rs index 25c2333e..bc5f848b 100644 --- a/src/game.rs +++ b/src/game.rs @@ -446,7 +446,7 @@ impl Game { .map(|n| { self.cache .plugin(n) - .ok_or_else(|| SortPluginsError::PluginNotLoaded(n.to_string())) + .ok_or_else(|| SortPluginsError::PluginNotLoaded((*n).to_owned())) }) .collect::, _>>()?; @@ -646,7 +646,7 @@ fn find_archives_in_path( } let paths = std::fs::read_dir(parent_path)? - .filter_map(|e| e.ok()) + .filter_map(Result::ok) .filter(|e| { e.file_type().map(|f| f.is_file()).unwrap_or(false) && iends_with_ascii(&e.file_name().to_string_lossy(), archive_file_extension) @@ -838,8 +838,8 @@ mod tests { // The paths may contain forward slashes, which cmd doesn't accept, // so replace them. - let original = OsString::from(original.to_str().unwrap().replace("/", "\\")); - let link = OsString::from(link.to_str().unwrap().replace("/", "\\")); + let original = OsString::from(original.to_str().unwrap().replace('/', "\\")); + let link = OsString::from(link.to_str().unwrap().replace('/', "\\")); let status = std::process::Command::new("cmd") .args([ @@ -895,7 +895,7 @@ mod tests { relative_components .into_iter() - .map(|c| c.as_os_str()) + .map(Component::as_os_str) .collect() } @@ -966,9 +966,7 @@ mod tests { fn should_error_if_given_a_game_path_that_does_not_exist() { let game_path = Path::new("missing"); match Game::new(GameType::TES3, game_path) { - Err(GameHandleCreationError::NotADirectory(p)) => { - assert_eq!(game_path, p) - } + Err(GameHandleCreationError::NotADirectory(p)) => assert_eq!(game_path, p), _ => panic!("Expected a not-a-directory error"), } } @@ -1062,9 +1060,7 @@ mod tests { let game = Game::with_local_path(fixture.game_type, game_path, &fixture.local_path); match game { - Err(GameHandleCreationError::NotADirectory(p)) => { - assert_eq!(game_path, p) - } + Err(GameHandleCreationError::NotADirectory(p)) => assert_eq!(game_path, p), _ => panic!("Expected a not-a-directory error"), } } @@ -1089,9 +1085,7 @@ mod tests { let game = Game::with_local_path(fixture.game_type, &fixture.game_path, local_path); match game { - Err(GameHandleCreationError::NotADirectory(p)) => { - assert_eq!(local_path, p) - } + Err(GameHandleCreationError::NotADirectory(p)) => assert_eq!(local_path, p), _ => panic!("Expected a not-a-directory error"), } } @@ -1246,7 +1240,7 @@ mod tests { game.load_current_load_order_state().unwrap(); let mut load_order: Vec<_> = - game.load_order().iter().map(|s| s.to_string()).collect(); + game.load_order().iter().map(ToString::to_string).collect(); let filename = "plugin.esp"; let data_file_path = fixture @@ -1374,7 +1368,7 @@ mod tests { if game_type == GameType::OpenMW { std::fs::rename( &path, - path.with_file_name(format!("{}.ghost", BLANK_MASTER_DEPENDENT_ESM)), + path.with_file_name(format!("{BLANK_MASTER_DEPENDENT_ESM}.ghost")), ) .unwrap(); @@ -1625,10 +1619,10 @@ mod tests { match source.downcast_ref::().unwrap() { esplugin::Error::PluginMetadataNotFound(p) => { if game_type == GameType::Starfield { - assert_eq!(BLANK_FULL_ESM, p) + assert_eq!(BLANK_FULL_ESM, p); } else { - assert_eq!(BLANK_ESM, p) - }; + assert_eq!(BLANK_ESM, p); + } } _ => panic!("Unexpected esplugin error: {e}"), } @@ -1873,7 +1867,7 @@ mod tests { let path = fixture .data_path() - .join(format!("{}.ghost", BLANK_MASTER_DEPENDENT_ESM)); + .join(format!("{BLANK_MASTER_DEPENDENT_ESM}.ghost")); let plugins = game .load_plugins_common(&[&path], LoadScope::HeaderOnly) @@ -2007,9 +2001,10 @@ mod tests { game.load_current_load_order_state().unwrap(); - let mut load_order: Vec<_> = game.load_order().iter().map(|s| s.to_string()).collect(); + let mut load_order: Vec<_> = + game.load_order().iter().map(ToString::to_string).collect(); load_order.swap(7, 10); - let load_order: Vec<_> = load_order.iter().map(|s| s.as_str()).collect(); + let load_order: Vec<_> = load_order.iter().map(String::as_str).collect(); game.set_load_order(&load_order).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 1b880ca6..2d019c77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,9 @@ +#![deny(clippy::pedantic)] +// Allow a few clippy pedantic lints. +#![allow(clippy::doc_markdown)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::missing_errors_doc)] + mod archive; mod database; pub mod error; diff --git a/src/logging.rs b/src/logging.rs index 8b218dd8..86639da8 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -68,8 +68,7 @@ impl From for log::Level { LogLevel::Debug => log::Level::Debug, LogLevel::Info => log::Level::Info, LogLevel::Warning => log::Level::Warn, - LogLevel::Error => log::Level::Error, - LogLevel::Fatal => log::Level::Error, + LogLevel::Error | LogLevel::Fatal => log::Level::Error, } } } @@ -89,7 +88,7 @@ impl Logger { pub(crate) fn log(&self, level: LogLevel, message: &str) { if level >= self.level { - (self.callback)(level, message) + (self.callback)(level, message); } } @@ -166,7 +165,7 @@ pub(crate) fn format_details(error: &E) -> String { let mut details = error.to_string(); // The display string. if let Some(source) = error.source() { details += ": "; - details += &format_details(&source) + details += &format_details(&source); } details @@ -188,8 +187,6 @@ mod tests { #[test] fn should_support_a_function() { - let _lock = TEST_LOCK.lock().unwrap(); - static MESSAGES: LazyLock>> = LazyLock::new(|| Mutex::new(Vec::new())); @@ -199,6 +196,8 @@ mod tests { } } + let _lock = TEST_LOCK.lock().unwrap(); + set_logging_callback(callback); error!("Test message"); @@ -233,6 +232,15 @@ mod tests { #[test] fn set_logging_callback_should_be_callable_multiple_times() { + static MESSAGES: LazyLock>> = + LazyLock::new(|| Mutex::new(Vec::new())); + + fn callback_fn(level: LogLevel, message: &str) { + if let Ok(mut messages) = MESSAGES.lock() { + messages.push((level, message.to_string())); + } + } + let _lock = TEST_LOCK.lock().unwrap(); let callback = |_, _: &str| {}; @@ -255,15 +263,6 @@ mod tests { *messages.lock().unwrap() ); - static MESSAGES: LazyLock>> = - LazyLock::new(|| Mutex::new(Vec::new())); - - fn callback_fn(level: LogLevel, message: &str) { - if let Ok(mut messages) = MESSAGES.lock() { - messages.push((level, message.to_string())); - } - } - set_logging_callback(callback_fn); error!("Test message"); @@ -280,8 +279,6 @@ mod tests { #[test] fn should_set_the_level_used_to_filter_messages_passed_to_the_callback() { - let _lock = TEST_LOCK.lock().unwrap(); - static MESSAGES: LazyLock>> = LazyLock::new(|| Mutex::new(Vec::new())); @@ -291,6 +288,8 @@ mod tests { } } + let _lock = TEST_LOCK.lock().unwrap(); + set_logging_callback(callback); set_log_level(LogLevel::Warning); diff --git a/src/metadata/error.rs b/src/metadata/error.rs index e79a0f77..4d25761e 100644 --- a/src/metadata/error.rs +++ b/src/metadata/error.rs @@ -142,42 +142,34 @@ impl std::fmt::Display for MetadataParsingErrorReason { Self::InvalidCondition(b) => { write!(f, "the condition string \"{}\" is invalid", b.0) } - Self::MissingKey(key, yaml_object_type) => write!( - f, - "\"{}\" key in \"{}\" map is missing", - key, yaml_object_type - ), + Self::MissingKey(key, yaml_object_type) => { + write!(f, "\"{key}\" key in \"{yaml_object_type}\" map is missing") + } Self::InvalidRegex(_) => { write!(f, "invalid regex in \"name\" key") } Self::InvalidMultilingualMessageContents => MultilingualMessageContentsError.fmt(f), - Self::UnexpectedType(expected_type, yaml_object_type) => write!( - f, - "\"{}\" object must be {}", - yaml_object_type, expected_type - ), + Self::UnexpectedType(expected_type, yaml_object_type) => { + write!(f, "\"{yaml_object_type}\" object must be {expected_type}") + } Self::UnexpectedValueType(key, expected_type, yaml_object_type) => write!( f, - "\"{}\" key in \"{}\" map must be {}", - key, yaml_object_type, expected_type + "\"{key}\" key in \"{yaml_object_type}\" map must be {expected_type}" ), Self::MissingPlaceholder(sub, placeholder_index) => write!( f, - "failed to substitute \"{}\" into message, no placeholder {{{}}} was found", - sub, placeholder_index + "failed to substitute \"{sub}\" into message, no placeholder {{{placeholder_index}}} was found" ), Self::MissingSubstitution(placeholder) => write!( f, - "failed to substitute a value into message, no substitution was given for the placeholder \"{}\"", - placeholder + "failed to substitute a value into message, no substitution was given for the placeholder \"{placeholder}\"" ), Self::NonU32Number(i) => { - write!(f, "{} is not valid as a 32-bit unsigned integer", i) + write!(f, "{i} is not valid as a 32-bit unsigned integer") } Self::DuplicateEntry(id, yaml_object_type) => write!( f, - "more than one entry exists for {} \"{}\"", - yaml_object_type, id + "more than one entry exists for {yaml_object_type} \"{id}\"" ), Self::Other(m) => m.fmt(f), } @@ -239,15 +231,12 @@ pub struct LoadMetadataError { impl LoadMetadataError { pub(super) fn new(path: PathBuf, reason: MetadataDocumentParsingError) -> Self { - Self { - path: path.to_path_buf(), - reason, - } + Self { path, reason } } pub(super) fn from_io_error(path: PathBuf, error: std::io::Error) -> Self { Self { - path: path.to_path_buf(), + path, reason: MetadataDocumentParsingError::IoError(error), } } @@ -281,7 +270,7 @@ impl std::fmt::Display for MetadataDocumentParsingError { match self { Self::PathNotFound => write!(f, "path not found"), Self::NoDocuments => write!(f, "no YAML document found"), - Self::MoreThanOneDocument(n) => write!(f, "expected 1 YAML document, found {}", n), + Self::MoreThanOneDocument(n) => write!(f, "expected 1 YAML document, found {n}"), Self::IoError(_) => write!(f, "an I/O error occurred"), Self::MetadataParsingError(_) => write!(f, "a metadata parsing error occurred"), Self::YamlMergeKeyError(_) => { @@ -294,9 +283,7 @@ impl std::fmt::Display for MetadataDocumentParsingError { impl std::error::Error for MetadataDocumentParsingError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Self::PathNotFound => None, - Self::NoDocuments => None, - Self::MoreThanOneDocument(_) => None, + Self::PathNotFound | Self::NoDocuments | Self::MoreThanOneDocument(_) => None, Self::IoError(e) => Some(e), Self::MetadataParsingError(e) => Some(e), Self::YamlMergeKeyError(e) => Some(e), @@ -335,10 +322,10 @@ pub(super) struct YamlMergeKeyError { } impl YamlMergeKeyError { - pub(super) fn new(value: saphyr::MarkedYaml) -> Self { + pub(super) fn new(value: &saphyr::MarkedYaml) -> Self { let mut yaml = String::new(); - let unmarked_yaml = to_unmarked_yaml(&value); + let unmarked_yaml = to_unmarked_yaml(value); if saphyr::YamlEmitter::new(&mut yaml) .dump(&unmarked_yaml) @@ -350,7 +337,7 @@ impl YamlMergeKeyError { yaml = yaml.split_off(index); } } else { - yaml = format!("{:?}", yaml); + yaml = format!("{yaml:?}"); } YamlMergeKeyError { diff --git a/src/metadata/group.rs b/src/metadata/group.rs index 89483b3f..14615acd 100644 --- a/src/metadata/group.rs +++ b/src/metadata/group.rs @@ -78,8 +78,8 @@ impl std::default::Default for Group { fn default() -> Self { Self { name: Group::DEFAULT_NAME.into(), - description: Default::default(), - after_groups: Default::default(), + description: Option::default(), + after_groups: Box::default(), } } } @@ -98,7 +98,7 @@ impl TryFromYaml for Group { Ok(Group { name: name.into(), description: description.map(|d| d.1.into()), - after_groups: after.iter().map(|a| a.to_string()).collect(), + after_groups: after.into_iter().map(str::to_owned).collect(), }) } } diff --git a/src/metadata/message.rs b/src/metadata/message.rs index cf5070e7..c976a758 100644 --- a/src/metadata/message.rs +++ b/src/metadata/message.rs @@ -91,7 +91,7 @@ impl std::default::Default for MessageContent { #[must_use] fn default() -> Self { Self { - text: Default::default(), + text: Box::default(), language: MessageContent::DEFAULT_LANGUAGE.into(), } } @@ -343,12 +343,15 @@ impl TryFromYaml for Message { } for (index, sub) in subs.iter().enumerate() { - let placeholder = format!("{{{}}}", index); + let placeholder = format!("{{{index}}}"); if !mc.text.contains(&placeholder) { return Err(ParseMetadataError::new( value.span.start, - MetadataParsingErrorReason::MissingPlaceholder(sub.to_string(), index), + MetadataParsingErrorReason::MissingPlaceholder( + (*sub).to_owned(), + index, + ), )); } diff --git a/src/metadata/metadata_document.rs b/src/metadata/metadata_document.rs index be168d9f..c418a6f4 100644 --- a/src/metadata/metadata_document.rs +++ b/src/metadata/metadata_document.rs @@ -104,16 +104,13 @@ impl MetadataDocument { } let doc = process_merge_keys(doc)?; - let doc = match doc.data { - YamlData::Mapping(h) => h, - _ => { - return Err(ParseMetadataError::unexpected_type( - doc.span.start, - YamlObjectType::MetadataDocument, - ExpectedType::Map, - ) - .into()); - } + let YamlData::Mapping(doc) = doc.data else { + return Err(ParseMetadataError::unexpected_type( + doc.span.start, + YamlObjectType::MetadataDocument, + ExpectedType::Map, + ) + .into()); }; let mut plugins: HashMap = HashMap::new(); @@ -292,12 +289,12 @@ impl MetadataDocument { // Ensure that the default group is present. let default_group_exists = groups.iter().any(|g| g.name() == Group::DEFAULT_NAME); - if !default_group_exists { + if default_group_exists { + self.groups = groups; + } else { self.groups.clear(); self.groups.push(Group::default()); self.groups.extend(groups); - } else { - self.groups = groups; } } @@ -328,11 +325,11 @@ impl MetadataDocument { impl std::default::Default for MetadataDocument { fn default() -> Self { Self { - bash_tags: Default::default(), + bash_tags: Vec::default(), groups: vec![Group::default()], - messages: Default::default(), - plugins: Default::default(), - regex_plugins: Default::default(), + messages: Vec::default(), + plugins: HashMap::default(), + regex_plugins: Vec::default(), } } } @@ -349,7 +346,7 @@ fn replace_prelude(masterlist: String, prelude: &str) -> String { } fn detect_line_ending(masterlist: &str) -> &'static str { - if let Some(pos) = masterlist.rfind("\n") { + if let Some(pos) = masterlist.rfind('\n') { if pos == 0 { "\n" } else if masterlist.as_bytes()[pos - 1] == b'\r' { @@ -393,8 +390,8 @@ fn find_prelude_bounds(masterlist: &str) -> Option<(usize, usize)> { } fn indent_prelude(prelude: &str, line_ending: &str) -> String { - let prelude = ("\n ".to_string() + &prelude.replace("\n", "\n ")) - .replace(&format!(" {}", line_ending), line_ending); + let prelude = ("\n ".to_string() + &prelude.replace('\n', "\n ")) + .replace(&format!(" {line_ending}"), line_ending); if prelude.ends_with("\n ") { prelude[..prelude.len() - 2].to_string() @@ -459,7 +456,7 @@ plugins: #[test] fn load_from_str_should_resolve_aliases() { - let yaml = r#" + let yaml = r" prelude: - &anchor type: say @@ -467,7 +464,7 @@ plugins: globals: - *anchor - "#; + "; let mut metadata_list = MetadataDocument::default(); metadata_list.load_from_str(yaml).unwrap(); @@ -492,7 +489,7 @@ plugins: #[test] fn load_from_str_should_error_if_a_plugin_has_two_exact_entries() { - let yaml = r#" + let yaml = r" plugins: - name: 'Blank.esm' msg: @@ -503,7 +500,7 @@ plugins: msg: - type: error content: 'This plugin entry will cause a failure, as it is not the first exact entry.' - "#; + "; let mut metadata_list = MetadataDocument::default(); assert!(metadata_list.load_from_str(yaml).is_err()); @@ -547,7 +544,7 @@ plugins: fn load_should_error_if_an_invalid_metadata_file_is_given() { let tmp_dir = tempdir().unwrap(); let path = tmp_dir.path().join("masterlist.yaml"); - let yaml = r#" + let yaml = r" - 'C.Climate' - 'Relev' @@ -559,7 +556,7 @@ plugins: - name: 'Blank.+\.esp' after: - 'Blank.esm' - "#; + "; std::fs::write(&path, yaml).unwrap(); diff --git a/src/metadata/plugin_cleaning_data.rs b/src/metadata/plugin_cleaning_data.rs index 6c090189..ee3f28ee 100644 --- a/src/metadata/plugin_cleaning_data.rs +++ b/src/metadata/plugin_cleaning_data.rs @@ -140,15 +140,12 @@ impl TryFromYaml for PluginCleaningData { fn try_from_yaml(value: &MarkedYaml) -> Result { let mapping = as_mapping(value, YamlObjectType::PluginCleaningData)?; - let crc = match get_u32_value(mapping, "crc", YamlObjectType::PluginCleaningData)? { - Some(n) => n, - None => { - return Err(ParseMetadataError::missing_key( - value.span.start, - "crc", - YamlObjectType::PluginCleaningData, - )); - } + let Some(crc) = get_u32_value(mapping, "crc", YamlObjectType::PluginCleaningData)? else { + return Err(ParseMetadataError::missing_key( + value.span.start, + "crc", + YamlObjectType::PluginCleaningData, + )); }; let util = get_required_string_value( @@ -285,7 +282,7 @@ mod tests { let data = PluginCleaningData::try_from_yaml(&yaml).unwrap(); - assert_eq!(0x12345678, data.crc()); + assert_eq!(0x1234_5678, data.crc()); assert_eq!("cleaner", data.cleaning_utility()); assert_eq!(&[MessageContent::new("info".into())], data.detail()); assert_eq!(2, data.itm_count()); @@ -299,7 +296,7 @@ mod tests { let data = PluginCleaningData::try_from_yaml(&yaml).unwrap(); - assert_eq!(0x12345678, data.crc()); + assert_eq!(0x1234_5678, data.crc()); assert_eq!("cleaner", data.cleaning_utility()); assert!(data.detail().is_empty()); assert_eq!(0, data.itm_count()); @@ -354,7 +351,7 @@ mod tests { #[test] fn should_omit_zero_counts() { - let data = PluginCleaningData::new(0xDEADBEEF, "TES5Edit".into()); + let data = PluginCleaningData::new(0xDEAD_BEEF, "TES5Edit".into()); let yaml = emit(&data); assert_eq!("crc: 0xDEADBEEF\nutil: 'TES5Edit'", yaml); @@ -362,7 +359,7 @@ mod tests { #[test] fn should_emit_non_zero_counts() { - let data = PluginCleaningData::new(0xDEADBEEF, "TES5Edit".into()) + let data = PluginCleaningData::new(0xDEAD_BEEF, "TES5Edit".into()) .with_itm_count(1) .with_deleted_reference_count(2) .with_deleted_navmesh_count(3); @@ -376,7 +373,7 @@ mod tests { #[test] fn should_emit_map_with_a_detail_string_if_detail_is_monolingual() { - let data = PluginCleaningData::new(0xDEADBEEF, "TES5Edit".into()) + let data = PluginCleaningData::new(0xDEAD_BEEF, "TES5Edit".into()) .with_detail(vec![MessageContent::new("message".into())]) .unwrap(); let yaml = emit(&data); @@ -392,7 +389,7 @@ mod tests { #[test] fn should_emit_map_with_a_detail_array_if_detail_is_multilingual() { - let data = PluginCleaningData::new(0xDEADBEEF, "TES5Edit".into()) + let data = PluginCleaningData::new(0xDEAD_BEEF, "TES5Edit".into()) .with_detail(vec![ MessageContent::new("english".into()).with_language("en".into()), MessageContent::new("french".into()).with_language("fr".into()), @@ -420,7 +417,7 @@ detail: #[test] fn should_emit_map_with_all_fields_set() { - let data = PluginCleaningData::new(0xDEADBEEF, "TES5Edit".into()) + let data = PluginCleaningData::new(0xDEAD_BEEF, "TES5Edit".into()) .with_itm_count(1) .with_deleted_reference_count(2) .with_deleted_navmesh_count(3) diff --git a/src/metadata/plugin_metadata.rs b/src/metadata/plugin_metadata.rs index 1f2bfd9d..f60265bd 100644 --- a/src/metadata/plugin_metadata.rs +++ b/src/metadata/plugin_metadata.rs @@ -99,13 +99,13 @@ impl PluginMetadata { /// Set the plugin's group. pub fn set_group(&mut self, group: String) { - self.group = Some(group.into_boxed_str()) + self.group = Some(group.into_boxed_str()); } /// Unsets the plugin's group, so that it is implicitly a member of the /// default group. pub fn unset_group(&mut self) { - self.group = None + self.group = None; } /// Get the plugins that the plugin must load after. @@ -159,7 +159,7 @@ impl PluginMetadata { } if self.group.is_none() && plugin.group.is_some() { - self.group = plugin.group.clone(); + self.group.clone_from(&plugin.group); } merge_slices(&mut self.load_after, &plugin.load_after); @@ -344,11 +344,11 @@ fn merge_slices(target: &mut Box<[T]>, source: &[T]) { let mut vec = target.to_vec(); for element in source { if !target.contains(element) { - vec.push(element.clone()) + vec.push(element.clone()); } } - *target = vec.into_boxed_slice() + *target = vec.into_boxed_slice(); } fn replace_capturing_groups(regex_string: &str) -> Cow<'_, str> { @@ -356,9 +356,8 @@ fn replace_capturing_groups(regex_string: &str) -> Cow<'_, str> { let mut prefix_length = 0; let mut remainder = regex_string; while let Some(pos) = remainder.find('(') { - let (before, after) = match remainder.split_at_checked(pos + 1) { - Some(t) => t, - None => break, + let Some((before, after)) = remainder.split_at_checked(pos + 1) else { + break; }; if after.starts_with('?') || (before.ends_with("\\(") && !before.ends_with("\\\\(")) { @@ -679,9 +678,9 @@ mod tests { let mut plugin1 = PluginMetadata::new(BLANK_ESM).unwrap(); let mut plugin2 = PluginMetadata::new(BLANK_ESM).unwrap(); - let data1 = PluginCleaningData::new(0x12345678, "util1".into()); - let data2 = PluginCleaningData::new(0xDEADBEEF, "util2".into()); - let data3 = PluginCleaningData::new(0xFEEDCAFE, "util3".into()); + let data1 = PluginCleaningData::new(0x1234_5678, "util1".into()); + let data2 = PluginCleaningData::new(0xDEAD_BEEF, "util2".into()); + let data3 = PluginCleaningData::new(0xFEED_CAFE, "util3".into()); plugin1.set_dirty_info(vec![data1.clone(), data2.clone()]); plugin2.set_dirty_info(vec![data1.clone(), data3.clone()]); @@ -698,9 +697,9 @@ mod tests { let mut plugin1 = PluginMetadata::new(BLANK_ESM).unwrap(); let mut plugin2 = PluginMetadata::new(BLANK_ESM).unwrap(); - let data1 = PluginCleaningData::new(0x12345678, "util1".into()); - let data2 = PluginCleaningData::new(0xDEADBEEF, "util2".into()); - let data3 = PluginCleaningData::new(0xFEEDCAFE, "util3".into()); + let data1 = PluginCleaningData::new(0x1234_5678, "util1".into()); + let data2 = PluginCleaningData::new(0xDEAD_BEEF, "util2".into()); + let data3 = PluginCleaningData::new(0xFEED_CAFE, "util3".into()); plugin1.set_clean_info(vec![data1.clone(), data2.clone()]); plugin2.set_clean_info(vec![data1.clone(), data3.clone()]); @@ -801,7 +800,7 @@ mod tests { #[test] fn should_be_false_if_dirty_info_is_set() { let mut plugin = PluginMetadata::new(BLANK_ESM).unwrap(); - plugin.set_dirty_info(vec![PluginCleaningData::new(0x12345678, "util1".into())]); + plugin.set_dirty_info(vec![PluginCleaningData::new(0x1234_5678, "util1".into())]); assert!(!plugin.has_name_only()); } @@ -809,7 +808,7 @@ mod tests { #[test] fn should_be_false_if_clean_info_is_set() { let mut plugin = PluginMetadata::new(BLANK_ESM).unwrap(); - plugin.set_clean_info(vec![PluginCleaningData::new(0x12345678, "util1".into())]); + plugin.set_clean_info(vec![PluginCleaningData::new(0x1234_5678, "util1".into())]); assert!(!plugin.has_name_only()); } @@ -1172,7 +1171,7 @@ mod tests { #[test] fn should_emit_dirty_info() { let mut plugin = PluginMetadata::new("test.esp").unwrap(); - plugin.set_dirty_info(vec![PluginCleaningData::new(0xDEADBEEF, "utility".into())]); + plugin.set_dirty_info(vec![PluginCleaningData::new(0xDEAD_BEEF, "utility".into())]); let yaml = emit(&plugin); assert_eq!( @@ -1189,7 +1188,7 @@ mod tests { #[test] fn should_emit_clean_info() { let mut plugin = PluginMetadata::new("test.esp").unwrap(); - plugin.set_clean_info(vec![PluginCleaningData::new(0xDEADBEEF, "utility".into())]); + plugin.set_clean_info(vec![PluginCleaningData::new(0xDEAD_BEEF, "utility".into())]); let yaml = emit(&plugin); assert_eq!( @@ -1238,7 +1237,7 @@ mod tests { match replace_capturing_groups(input) { Cow::Borrowed(output) => assert_eq!(input, output), - Cow::Owned(output) => panic!("Expected borrowed output, got {}", output), + Cow::Owned(output) => panic!("Expected borrowed output, got {output}"), } } @@ -1248,14 +1247,14 @@ mod tests { match replace_capturing_groups(input) { Cow::Borrowed(output) => assert_eq!(input, output), - Cow::Owned(output) => panic!("Expected borrowed output, got {}", output), + Cow::Owned(output) => panic!("Expected borrowed output, got {output}"), } let input = "no paren(?:th(?:e)s)es"; match replace_capturing_groups(input) { Cow::Borrowed(output) => assert_eq!(input, output), - Cow::Owned(output) => panic!("Expected borrowed output, got {}", output), + Cow::Owned(output) => panic!("Expected borrowed output, got {output}"), } } diff --git a/src/metadata/yaml/emit.rs b/src/metadata/yaml/emit.rs index 01f94dd0..5cbbafcb 100644 --- a/src/metadata/yaml/emit.rs +++ b/src/metadata/yaml/emit.rs @@ -106,7 +106,7 @@ impl YamlEmitter { _ => self.scope.push(YamlBlock::Map), } - self.write(&format!("{}:", key)); + self.write(&format!("{key}:")); } pub fn begin_array(&mut self) { @@ -298,7 +298,7 @@ fn double_quote(value: &str) -> String { }) .collect(); - format!("\"{}\"", escaped) + format!("\"{escaped}\"") } impl EmitYaml for &[T] { @@ -315,7 +315,7 @@ impl EmitYaml for &[T] { elements => { emitter.begin_array(); - for element in elements.iter() { + for element in *elements { element.emit_yaml(emitter); } diff --git a/src/metadata/yaml/merge.rs b/src/metadata/yaml/merge.rs index cbfdca4e..4322bfc3 100644 --- a/src/metadata/yaml/merge.rs +++ b/src/metadata/yaml/merge.rs @@ -55,11 +55,11 @@ fn merge_into_mapping<'a, 'b>( if let YamlData::Mapping(h) = e.data { Ok(merge_mappings(acc, h)) } else { - Err(YamlMergeKeyError::new(e)) + Err(YamlMergeKeyError::new(&e)) } }), YamlData::Mapping(h) => Ok(merge_mappings(mapping, h)), - _ => Err(YamlMergeKeyError::new(value)), + _ => Err(YamlMergeKeyError::new(&value)), } } diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 178f7619..6131ec6f 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -123,7 +123,9 @@ impl Plugin { /// and OpenMW) or if the `HEDR` subrecord could not be found, of if the /// version field's value was `NaN`. pub fn header_version(&self) -> Option { - self.plugin.as_ref().and_then(|p| p.header_version()) + self.plugin + .as_ref() + .and_then(esplugin::Plugin::header_version) } /// Get the plugin's version number from its description field. @@ -140,8 +142,7 @@ impl Plugin { pub fn masters(&self) -> Result, PluginDataError> { self.plugin .as_ref() - .map(|p| p.masters().map_err(Into::into)) - .unwrap_or_else(|| Ok(Vec::new())) + .map_or_else(|| Ok(Vec::new()), |p| p.masters().map_err(Into::into)) } /// Get any Bash Tags found in the plugin's description field. @@ -174,8 +175,7 @@ impl Plugin { } else { self.plugin .as_ref() - .map(|p| p.is_master_file()) - .unwrap_or(false) + .is_some_and(esplugin::Plugin::is_master_file) } } @@ -183,56 +183,49 @@ impl Plugin { pub fn is_light_plugin(&self) -> bool { self.plugin .as_ref() - .map(|p| p.is_light_plugin()) - .unwrap_or(false) + .is_some_and(esplugin::Plugin::is_light_plugin) } /// Check if the plugin is a medium plugin. pub fn is_medium_plugin(&self) -> bool { self.plugin .as_ref() - .map(|p| p.is_medium_plugin()) - .unwrap_or(false) + .is_some_and(esplugin::Plugin::is_medium_plugin) } /// Check if the plugin is an update plugin. pub fn is_update_plugin(&self) -> bool { self.plugin .as_ref() - .map(|p| p.is_update_plugin()) - .unwrap_or(false) + .is_some_and(esplugin::Plugin::is_update_plugin) } /// Check if the plugin is a blueprint plugin. pub fn is_blueprint_plugin(&self) -> bool { self.plugin .as_ref() - .map(|p| p.is_blueprint_plugin()) - .unwrap_or(false) + .is_some_and(esplugin::Plugin::is_blueprint_plugin) } /// Check if the plugin is or would be valid as a light plugin. pub fn is_valid_as_light_plugin(&self) -> Result { - self.plugin - .as_ref() - .map(|p| p.is_valid_as_light_plugin().map_err(Into::into)) - .unwrap_or(Ok(false)) + self.plugin.as_ref().map_or(Ok(false), |p| { + p.is_valid_as_light_plugin().map_err(Into::into) + }) } /// Check if the plugin is or would be valid as a medium plugin. pub fn is_valid_as_medium_plugin(&self) -> Result { - self.plugin - .as_ref() - .map(|p| p.is_valid_as_medium_plugin().map_err(Into::into)) - .unwrap_or(Ok(false)) + self.plugin.as_ref().map_or(Ok(false), |p| { + p.is_valid_as_medium_plugin().map_err(Into::into) + }) } /// Check if the plugin is or would be valid as an update plugin. pub fn is_valid_as_update_plugin(&self) -> Result { - self.plugin - .as_ref() - .map(|p| p.is_valid_as_update_plugin().map_err(Into::into)) - .unwrap_or(Ok(false)) + self.plugin.as_ref().map_or(Ok(false), |p| { + p.is_valid_as_update_plugin().map_err(Into::into) + }) } /// Check if the plugin contains any records other than its `TES3`/`TES4` @@ -240,7 +233,7 @@ impl Plugin { pub fn is_empty(&self) -> bool { self.plugin .as_ref() - .and_then(|p| p.record_and_group_count()) + .and_then(esplugin::Plugin::record_and_group_count) .unwrap_or(0) == 0 } @@ -265,8 +258,7 @@ impl Plugin { pub(crate) fn override_record_count(&self) -> Result { self.plugin .as_ref() - .map(|p| p.count_override_records().map_err(Into::into)) - .unwrap_or(Ok(0)) + .map_or(Ok(0), |p| p.count_override_records().map_err(Into::into)) } pub(crate) fn asset_count(&self) -> usize { @@ -353,8 +345,7 @@ fn has_plugin_file_extension(game_type: GameType, plugin_path: &Path) -> bool { pub(crate) fn has_ascii_extension(path: &Path, extension: &str) -> bool { path.extension() - .map(|e| e.eq_ignore_ascii_case(extension)) - .unwrap_or(false) + .is_some_and(|e| e.eq_ignore_ascii_case(extension)) } pub(crate) fn plugins_metadata( @@ -404,7 +395,7 @@ fn extract_bash_tags(description: &str) -> Vec { if let Some(end_pos) = description[start_pos..].find("}}") { return description[start_pos..start_pos + end_pos] - .split(",") + .split(',') .map(|s| s.trim().to_string()) .collect(); } @@ -430,9 +421,9 @@ fn extract_version(description: &str) -> Result, Box Result, Box { - assert_eq!(1.2, plugin.header_version().unwrap()) + assert_eq!(1.2, plugin.header_version().unwrap()); } GameType::TES4 => assert_eq!(0.8, plugin.header_version().unwrap()), GameType::Starfield => assert_eq!(0.96, plugin.header_version().unwrap()), @@ -619,9 +611,10 @@ mod tests { assert!(!plugin.is_empty()); assert!(plugin.version().is_none()); + #[expect(clippy::float_cmp, reason = "float values should be exactly equal")] match game_type { GameType::TES3 | GameType::OpenMW => { - assert_eq!(1.2, plugin.header_version().unwrap()) + assert_eq!(1.2, plugin.header_version().unwrap()); } GameType::TES4 => assert_eq!(0.8, plugin.header_version().unwrap()), GameType::Starfield => assert_eq!(0.96, plugin.header_version().unwrap()), @@ -629,10 +622,10 @@ mod tests { } let expected_crc = match game_type { - GameType::TES3 | GameType::OpenMW => 3317676987, - GameType::Starfield => 1422425298, - GameType::TES4 => 3759349588, - _ => 3000242590, + GameType::TES3 | GameType::OpenMW => 3_317_676_987, + GameType::Starfield => 1_422_425_298, + GameType::TES4 => 3_759_349_588, + _ => 3_000_242_590, }; assert_eq!(expected_crc, plugin.crc().unwrap()); diff --git a/src/sorting/error.rs b/src/sorting/error.rs index 2115c840..dafec336 100644 --- a/src/sorting/error.rs +++ b/src/sorting/error.rs @@ -43,7 +43,7 @@ impl CyclicInteractionError { impl Display for CyclicInteractionError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let cycle = display_cycle(&self.cycle); - write!(f, "cyclic interaction detected: {}", cycle) + write!(f, "cyclic interaction detected: {cycle}") } } @@ -75,7 +75,7 @@ pub enum GroupsPathError { impl Display for GroupsPathError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::UndefinedGroup(g) => write!(f, "the group \"{}\" does not exist", g), + Self::UndefinedGroup(g) => write!(f, "the group \"{g}\" does not exist"), Self::CycleFound(c) => write!(f, "found a cycle: {}", display_cycle(c)), Self::PathfindingError(_) => write!(f, "failed to find a path in the groups graph"), } @@ -168,21 +168,18 @@ impl Display for PathfindingError { ), Self::PrecedingNodeNotFound(n) => write!( f, - "unexpectedly could not find the node before \"{}\" in the path that was found", - n + "unexpectedly could not find the node before \"{n}\" in the path that was found", ), Self::FollowingNodeNotFound(n) => write!( f, - "unexpectedly could not find the node after \"{}\" in the path that was found", - n + "unexpectedly could not find the node after \"{n}\" in the path that was found", ), Self::EdgeNotFound { from_group, to_group, } => write!( f, - "unexpectedly could not find the edge going from \"{}\" to \"{}\"", - from_group, to_group + "unexpectedly could not find the edge going from \"{from_group}\" to \"{to_group}\"", ), } } @@ -242,7 +239,7 @@ impl Display for SortingError { Self::ValidationError(_) => write!(f, "plugin graph validation failed"), Self::UndefinedGroup(_) => write!(f, "found an undefined group"), Self::CycleFound(_) => write!(f, "found a cycle"), - Self::CycleInvolving(n) => write!(f, "found a cycle involving \"{}\"", n), + Self::CycleInvolving(n) => write!(f, "found a cycle involving \"{n}\""), Self::PluginDataError(_) => write!(f, "failed to read plugin data"), Self::PathfindingError(_) => write!(f, "failed to find a path in the plugins graph"), } diff --git a/src/sorting/groups.rs b/src/sorting/groups.rs index 66bde9a0..07cff385 100644 --- a/src/sorting/groups.rs +++ b/src/sorting/groups.rs @@ -103,8 +103,8 @@ fn add_groups<'a>( } fn sorted_clone(strings: &[String]) -> Vec<&str> { - let mut strings: Vec<_> = strings.iter().map(|s| s.as_str()).collect(); - strings.sort(); + let mut strings: Vec<_> = strings.iter().map(String::as_str).collect(); + strings.sort_unstable(); strings } @@ -119,7 +119,7 @@ pub fn find_path( |_, e| { if *e == EdgeType::UserLoadAfter { // A very small number so that user edges are practically always preferred. - -1000000.0 + -1_000_000.0 } else { 1.0 } @@ -163,15 +163,12 @@ pub fn find_path( return Ok(Vec::new()); } - let edge = match graph.find_edge(*preceding_vertex, current) { - Some(e) => e, - None => { - return Err(PathfindingError::EdgeNotFound { - from_group: graph[*preceding_vertex].clone().into_string(), - to_group: graph[current].clone().into_string(), - } - .into()); + let Some(edge) = graph.find_edge(*preceding_vertex, current) else { + return Err(PathfindingError::EdgeNotFound { + from_group: graph[*preceding_vertex].clone().into_string(), + to_group: graph[current].clone().into_string(), } + .into()); }; let vertex = Vertex::new(graph[*preceding_vertex].clone().into_string()) @@ -190,17 +187,14 @@ fn find_node_by_weight( graph: &Graph, EdgeType>, weight: &str, ) -> Result { - match graph.node_indices().find(|i| { - graph - .node_weight(*i) - .map(|w| w.as_ref() == weight) - .unwrap_or(false) - }) { - Some(n) => Ok(n), - None => { - logging::error!("Can't find group with name {}", weight); - Err(UndefinedGroupError::new(weight.to_string())) - } + if let Some(n) = graph + .node_indices() + .find(|i| graph.node_weight(*i).is_some_and(|w| w.as_ref() == weight)) + { + Ok(n) + } else { + logging::error!("Can't find group with name {}", weight); + Err(UndefinedGroupError::new(weight.to_string())) } } @@ -243,7 +237,7 @@ struct GroupsPathLengthVisitor { impl GroupsPathLengthVisitor { fn new() -> Self { - Default::default() + GroupsPathLengthVisitor::default() } fn max_path_length(&self) -> usize { @@ -261,7 +255,7 @@ impl<'a> DfsVisitor<'a> for GroupsPathLengthVisitor { fn discover_node(&mut self, _: NodeIndex) { self.current_path_length += 1; if self.current_path_length > self.max_path_length { - self.max_path_length = self.current_path_length + self.max_path_length = self.current_path_length; } } @@ -290,7 +284,7 @@ mod tests { match build_groups_graph(groups, &[]) { Err(BuildGroupsGraphError::UndefinedGroup(e)) => { - assert_eq!("a", e.into_group_name()) + assert_eq!("a", e.into_group_name()); } _ => panic!("Expected an undefined group error"), } @@ -303,7 +297,7 @@ mod tests { match build_groups_graph(masterlist, userlist) { Err(BuildGroupsGraphError::UndefinedGroup(e)) => { - assert_eq!("a", e.into_group_name()) + assert_eq!("a", e.into_group_name()); } _ => panic!("Expected an undefined group error"), } diff --git a/src/sorting/plugins.rs b/src/sorting/plugins.rs index 2798d776..601d6ba0 100644 --- a/src/sorting/plugins.rs +++ b/src/sorting/plugins.rs @@ -416,16 +416,16 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> { // Assets don't overlap or both plugins load the same number of // assets, don't add an edge. continue; - } else { - outer_plugin_loads_first = plugin_asset_count > other_plugin_asset_count; - edge_type = EdgeType::AssetOverlap; } + + outer_plugin_loads_first = plugin_asset_count > other_plugin_asset_count; + edge_type = EdgeType::AssetOverlap; } else { // Records overlap and override different numbers of records. // Load this plugin first if it overrides more records. outer_plugin_loads_first = plugin.override_record_count > other_plugin.override_record_count; - edge_type = EdgeType::RecordOverlap + edge_type = EdgeType::RecordOverlap; } let (from_index, to_index) = if outer_plugin_loads_first { @@ -435,15 +435,15 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> { }; if !self.is_path_cached(from_index, to_index) { - if !self.path_exists(to_index, from_index) { - self.add_edge(from_index, to_index, edge_type); - } else { + if self.path_exists(to_index, from_index) { logging::debug!( "Skipping {} edge from \"{}\" to \"{}\" as it would create a cycle.", edge_type, self[from_index].name(), self[to_index].name() ); + } else { + self.add_edge(from_index, to_index, edge_type); } } } @@ -653,7 +653,7 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> { // Insert position is just after the found vertex, and a forward iterator // points to the element one after the element pointed to by the // corresponding reverse iterator. - let insert_position = previous_node_position.map(|i| i + 1).unwrap_or(range_start); + let insert_position = previous_node_position.map_or(range_start, |i| i + 1); // Add an edge going from this vertex to the next one in the "new load // order" path, in case there isn't already one. @@ -711,10 +711,7 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> { } fn is_path_cached(&self, from: NodeIndex, to: NodeIndex) -> bool { - self.paths_cache - .get(&from) - .map(|s| s.contains(&to)) - .unwrap_or(false) + self.paths_cache.get(&from).is_some_and(|s| s.contains(&to)) } fn node_index_by_name(&self, name: &str) -> Option { @@ -751,8 +748,8 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> { impl std::default::Default for PluginsGraph<'_, T> { fn default() -> Self { Self { - inner: Default::default(), - paths_cache: Default::default(), + inner: Graph::default(), + paths_cache: HashMap::default(), } } } @@ -974,7 +971,7 @@ impl BidirBfsVisitor for PathFinder<'_, '_, T> { } fn visit_intersection_node(&mut self, node: NodeIndex) { - self.intersection_node = Some(node) + self.intersection_node = Some(node); } } @@ -1081,7 +1078,7 @@ impl<'a, 'b, 'c, 'd, 'e, T: SortingPlugin> GroupsPathVisitor<'a, 'b, 'c, 'd, 'e, fn find_plugins_in_group(&self, node_index: GroupNodeIndex) -> &'c [PluginNodeIndex] { self.groups_plugins .get(self.groups_graph[node_index].as_ref()) - .map(|v| v.as_slice()) + .map(Vec::as_slice) .unwrap_or_default() } @@ -1123,16 +1120,16 @@ impl<'a, 'b, 'c, 'd, 'e, T: SortingPlugin> GroupsPathVisitor<'a, 'b, 'c, 'd, 'e, EdgeType::MasterlistGroup }; - if !self.plugins_graph.path_exists(*to_plugin, from_plugin) { - self.plugins_graph - .add_edge(from_plugin, *to_plugin, edge_type); - } else { + if self.plugins_graph.path_exists(*to_plugin, from_plugin) { logging::debug!( "Skipping a \"{}\" edge from \"{}\" to \"{}\" as it would create a cycle.", edge_type, self.plugins_graph[from_plugin].name(), self.plugins_graph[*to_plugin].name() ); + } else { + self.plugins_graph + .add_edge(from_plugin, *to_plugin, edge_type); } } } @@ -1222,6 +1219,7 @@ impl<'e, T: SortingPlugin> DfsVisitor<'e> for GroupsPathVisitor<'_, '_, '_, '_, #[cfg(test)] mod tests { + #![allow(clippy::many_single_char_names)] use super::*; use crate::sorting::{groups::build_groups_graph, test::TestPlugin}; @@ -1252,7 +1250,7 @@ mod tests { plugins: plugin_names .iter() .enumerate() - .map(|(i, n)| (n.to_string(), (TestPlugin::new(n), i))) + .map(|(i, n)| ((*n).to_owned(), (TestPlugin::new(n), i))) .collect(), } } diff --git a/src/tests.rs b/src/tests.rs index b7a4be8c..af611f5e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -119,8 +119,7 @@ fn set_load_order( use std::io::Write; match game_type { - GameType::TES3 => {} - GameType::OpenMW => {} + GameType::TES3 | GameType::OpenMW => {} _ => { let mut file = File::create(local_path.join("Plugins.txt")).unwrap(); for (plugin, is_active) in load_order { @@ -140,7 +139,7 @@ fn set_load_order( if is_load_order_timestamp_based(game_type) { let mut mod_time = SystemTime::now(); for (plugin, _) in load_order { - let ghosted_path = data_path.join(plugin.to_string() + ".ghost"); + let ghosted_path = data_path.join(format!("{plugin}.ghost")); let file = if ghosted_path.exists() { File::options().write(true).open(ghosted_path) } else {