Skip to content

Commit

Permalink
Improve case insensitivity
Browse files Browse the repository at this point in the history
Lowercasing strings to isn't the correct way to perform case-insensitive
matching, so use unicase to do case folding.
  • Loading branch information
Ortham committed Oct 15, 2023
1 parent d67df83 commit 2564b1c
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 33 deletions.
8 changes: 4 additions & 4 deletions src/load_order/asterisk_based.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::collections::HashSet;
use std::fs::File;
use std::io::{BufWriter, Write};

use unicase::eq;
use unicase::{eq, UniCase};

use super::mutable::{generic_insert_position, hoist_masters, read_plugin_names, MutableLoadOrder};
use super::readable::{ReadableLoadOrder, ReadableLoadOrderBase};
Expand Down Expand Up @@ -217,15 +217,15 @@ impl WritableLoadOrder for AsteriskBasedLoadOrder {
/// An asterisk-based load order can be ambiguous if there are installed
/// plugins that are not implicitly active and not listed in plugins.txt.
fn is_ambiguous(&self) -> Result<bool, Error> {
let mut set: HashSet<String> = HashSet::new();
let mut set = HashSet::new();

// Read plugins from the active plugins file. A set of plugin names is
// more useful than the returned vec, so insert into the set during the
// line mapping and then discard the line.
if !self.ignore_active_plugins_file() {
read_plugin_names(self.game_settings().active_plugins_file(), |line| {
plugin_line_mapper(line).and_then::<(), _>(|(name, _)| {
set.insert(trim_dot_ghost(name).to_lowercase());
set.insert(UniCase::new(trim_dot_ghost(name).to_string()));
None
})
})?;
Expand All @@ -240,7 +240,7 @@ impl WritableLoadOrder for AsteriskBasedLoadOrder {
.plugins
.iter()
.filter(|plugin| !self.game_settings.is_implicitly_active(plugin.name()))
.all(|plugin| set.contains(&plugin.name().to_lowercase()));
.all(|plugin| set.contains(&UniCase::new(plugin.name().to_string())));

Ok(!plugins_listed)
}
Expand Down
35 changes: 18 additions & 17 deletions src/load_order/mutable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use std::path::{Path, PathBuf};

use encoding_rs::WINDOWS_1252;
use rayon::prelude::*;
use unicase::UniCase;

use super::readable::{ReadableLoadOrder, ReadableLoadOrderBase};
use crate::enums::Error;
Expand Down Expand Up @@ -243,8 +244,6 @@ pub fn generic_insert_position(plugins: &[Plugin], plugin: &Plugin) -> Option<us
}

fn find_plugins_in_dirs(directories: &[PathBuf], game: GameId) -> Vec<String> {
let mut set: HashSet<String> = HashSet::new();

let mut dir_entries: Vec<_> = directories
.iter()
.flat_map(read_dir)
Expand Down Expand Up @@ -272,10 +271,12 @@ fn find_plugins_in_dirs(directories: &[PathBuf], game: GameId) -> Vec<String> {
}
});

let mut set = HashSet::new();

dir_entries
.into_iter()
.filter_map(|e| e.file_name().to_str().map(str::to_owned))
.filter(|filename| set.insert(trim_dot_ghost(filename).to_lowercase()))
.filter(|filename| set.insert(UniCase::new(trim_dot_ghost(filename).to_string())))
.collect()
}

Expand Down Expand Up @@ -309,15 +310,15 @@ fn validate_master_file_index(
.rposition(|p| p.is_master_file())
.unwrap_or(0);

let master_names: HashSet<String> =
plugin.masters()?.iter().map(|m| m.to_lowercase()).collect();
let masters = plugin.masters()?;
let master_names: HashSet<_> = masters.iter().map(|m| UniCase::new(m.as_str())).collect();

// Check that all of the plugins that load between this index and
// the previous plugin are masters of this plugin.
if preceding_plugins
.iter()
.skip(previous_master_pos + 1)
.any(|p| !master_names.contains(&p.name().to_lowercase()))
.any(|p| !master_names.contains(&UniCase::new(p.name())))
{
return Err(Error::NonMasterBeforeMaster);
}
Expand All @@ -328,7 +329,7 @@ fn validate_master_file_index(
.iter()
.skip(index)
.filter(|p| !p.is_master_file())
.find(|p| master_names.contains(&p.name().to_lowercase()))
.find(|p| master_names.contains(&UniCase::new(p.name())))
{
Err(Error::UnrepresentedHoist {
plugin: p.name().to_string(),
Expand Down Expand Up @@ -441,8 +442,8 @@ fn get_plugin_to_insert_at<T: MutableLoadOrder + ?Sized>(
}

fn are_plugin_names_unique(plugin_names: &[&str]) -> bool {
let unique_plugin_names: HashSet<String> =
plugin_names.par_iter().map(|s| s.to_lowercase()).collect();
let unique_plugin_names: HashSet<_> =
plugin_names.par_iter().map(|s| UniCase::new(s)).collect();

unique_plugin_names.len() == plugin_names.len()
}
Expand All @@ -458,7 +459,7 @@ fn validate_load_order(plugins: &[Plugin]) -> Result<(), Error> {
Some(x) => x,
};

let mut plugin_names: HashSet<String> = HashSet::new();
let mut plugin_names: HashSet<_> = HashSet::new();

// Add each plugin that isn't a master file to the hashset.
// When a master file is encountered, remove its masters from the hashset.
Expand All @@ -471,10 +472,10 @@ fn validate_load_order(plugins: &[Plugin]) -> Result<(), Error> {
.take(last_master_pos - first_non_master_pos + 1)
{
if !plugin.is_master_file() {
plugin_names.insert(plugin.name().to_lowercase());
plugin_names.insert(UniCase::new(plugin.name().to_string()));
} else {
for master in plugin.masters()? {
plugin_names.remove(&master.to_lowercase());
plugin_names.remove(&UniCase::new(master.clone()));
}

if !plugin_names.is_empty() {
Expand All @@ -489,11 +490,11 @@ fn validate_load_order(plugins: &[Plugin]) -> Result<(), Error> {
plugin_names.clear();
for plugin in plugins.iter().rev() {
if !plugin.is_master_file() {
plugin_names.insert(plugin.name().to_lowercase());
plugin_names.insert(UniCase::new(plugin.name().to_string()));
} else if let Some(m) = plugin
.masters()?
.iter()
.find(|m| plugin_names.contains(&m.to_lowercase()))
.find(|m| plugin_names.contains(&UniCase::new(m.to_string())))
{
return Err(Error::UnrepresentedHoist {
plugin: m.clone(),
Expand All @@ -509,19 +510,19 @@ fn remove_duplicates_icase(
plugin_tuples: Vec<(String, bool)>,
filenames: Vec<String>,
) -> Vec<(String, bool)> {
let mut set: HashSet<String> = HashSet::with_capacity(filenames.len());
let mut set: HashSet<_> = HashSet::with_capacity(filenames.len());

let mut unique_tuples: Vec<(String, bool)> = plugin_tuples
.into_iter()
.rev()
.filter(|(string, _)| set.insert(trim_dot_ghost(string).to_lowercase()))
.filter(|(string, _)| set.insert(UniCase::new(trim_dot_ghost(string).to_string())))
.collect();

unique_tuples.reverse();

let unique_file_tuples_iter = filenames
.into_iter()
.filter(|string| set.insert(trim_dot_ghost(string).to_lowercase()))
.filter(|string| set.insert(UniCase::new(trim_dot_ghost(string).to_string())))
.map(|f| (f, false));

unique_tuples.extend(unique_file_tuples_iter);
Expand Down
10 changes: 5 additions & 5 deletions src/load_order/textfile_based.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::fs::File;
use std::io::{BufWriter, Read, Write};
use std::path::{Path, PathBuf};

use unicase::eq;
use unicase::{eq, UniCase};

use super::mutable::{
generic_insert_position, hoist_masters, load_active_plugins, plugin_line_mapper,
Expand Down Expand Up @@ -218,15 +218,15 @@ impl WritableLoadOrder for TextfileBasedLoadOrder {
}
};

let set: HashSet<String> = plugin_names
.into_iter()
.map(|name| trim_dot_ghost(&name).to_lowercase())
let set: HashSet<_> = plugin_names
.iter()
.map(|name| UniCase::new(trim_dot_ghost(&name)))
.collect();

let all_plugins_listed = self
.plugins
.iter()
.all(|plugin| set.contains(&plugin.name().to_lowercase()));
.all(|plugin| set.contains(&UniCase::new(plugin.name())));

Ok(!all_plugins_listed)
}
Expand Down
12 changes: 5 additions & 7 deletions src/load_order/writable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::collections::HashSet;
use std::fs::create_dir_all;
use std::path::Path;

use unicase::eq;
use unicase::{eq, UniCase};

use super::mutable::MutableLoadOrder;
use super::readable::{ReadableLoadOrder, ReadableLoadOrderBase};
Expand Down Expand Up @@ -100,18 +100,16 @@ pub fn remove<T: MutableLoadOrder>(load_order: &mut T, plugin_name: &str) -> Res
.rposition(|p| p.is_master_file())
.unwrap_or(0);

let master_names: HashSet<String> = load_order.plugins()[*position]
.masters()?
.iter()
.map(|m| m.to_lowercase())
.collect();
let masters = load_order.plugins()[*position].masters()?;
let master_names: HashSet<_> =
masters.iter().map(|m| UniCase::new(m.as_str())).collect();

if load_order
.plugins()
.iter()
.take(*position)
.skip(*previous_master_pos)
.any(|p| !master_names.contains(&p.name().to_lowercase()))
.any(|p| !master_names.contains(&UniCase::new(p.name())))
{
return Err(Error::NonMasterBeforeMaster);
}
Expand Down

0 comments on commit 2564b1c

Please sign in to comment.