-
Notifications
You must be signed in to change notification settings - Fork 8
feat(ltk_inibin): implement inibin crate with public api #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| //! Other utilities (hashing, etc) | ||
| pub mod elf; | ||
| pub mod fnv1a; | ||
| pub mod sdbm; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| /// Compute SDBM hash of a lowercased string. | ||
| pub fn hash_lower(input: impl AsRef<str>) -> u32 { | ||
| let mut hash: u32 = 0; | ||
| for c in input.as_ref().chars().flat_map(|c| c.to_lowercase()) { | ||
| let mut buf = [0u8; 4]; | ||
| let encoded = c.encode_utf8(&mut buf); | ||
| for &byte in encoded.as_bytes() { | ||
| hash = (byte as u32) | ||
| .wrapping_add(hash.wrapping_shl(6)) | ||
| .wrapping_add(hash.wrapping_shl(16)) | ||
| .wrapping_sub(hash); | ||
| } | ||
| } | ||
| hash | ||
| } | ||
|
|
||
| /// Compute SDBM hash of an inibin `section*property` key pair (lowercased, `*` delimiter). | ||
| /// | ||
| /// Convenience wrapper around [`hash_lower_with_delimiter`] with the standard inibin delimiter. | ||
| /// | ||
| /// ``` | ||
| /// # use ltk_hash::sdbm; | ||
| /// let key = sdbm::hash_inibin_key("DATA", "AttackRange"); | ||
| /// assert_eq!(key, sdbm::hash_lower_with_delimiter("DATA", "AttackRange", '*')); | ||
| /// ``` | ||
| pub fn hash_inibin_key(section: impl AsRef<str>, property: impl AsRef<str>) -> u32 { | ||
| hash_lower_with_delimiter(section, property, '*') | ||
| } | ||
|
|
||
| /// Compute SDBM hash of two strings joined by a delimiter, all lowercased. | ||
| /// | ||
| /// For inibin keys, prefer [`hash_inibin_key`] which defaults the `*` delimiter. | ||
| pub fn hash_lower_with_delimiter(a: impl AsRef<str>, b: impl AsRef<str>, delimiter: char) -> u32 { | ||
| let mut hash: u32 = 0; | ||
|
|
||
| let chars = a | ||
| .as_ref() | ||
| .chars() | ||
| .chain(std::iter::once(delimiter)) | ||
| .chain(b.as_ref().chars()) | ||
| .flat_map(|c| c.to_lowercase()); | ||
|
|
||
| for c in chars { | ||
| let mut buf = [0u8; 4]; | ||
| let encoded = c.encode_utf8(&mut buf); | ||
| for &byte in encoded.as_bytes() { | ||
| hash = (byte as u32) | ||
| .wrapping_add(hash.wrapping_shl(6)) | ||
| .wrapping_add(hash.wrapping_shl(16)) | ||
| .wrapping_sub(hash); | ||
| } | ||
| } | ||
| hash | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_hash_lower_basic() { | ||
| // SDBM hash of "test" (all lowercase) | ||
| let h = hash_lower("test"); | ||
| // Verify case-insensitivity | ||
| assert_eq!(h, hash_lower("TEST")); | ||
| assert_eq!(h, hash_lower("TeSt")); | ||
| } | ||
|
Comment on lines
+60
to
+67
|
||
|
|
||
| #[test] | ||
| fn test_hash_lower_empty() { | ||
| assert_eq!(hash_lower(""), 0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_hash_lower_with_delimiter() { | ||
| let h1 = hash_lower_with_delimiter("DATA", "AttackRange", '*'); | ||
| let h2 = hash_lower("data*attackrange"); | ||
| assert_eq!(h1, h2); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_hash_lower_with_delimiter_case_insensitive() { | ||
| let h1 = hash_lower_with_delimiter("DATA", "AttackRange", '*'); | ||
| let h2 = hash_lower_with_delimiter("data", "attackrange", '*'); | ||
| assert_eq!(h1, h2); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_hash_inibin_key() { | ||
| let h1 = hash_inibin_key("DATA", "AttackRange"); | ||
| let h2 = hash_lower_with_delimiter("DATA", "AttackRange", '*'); | ||
| assert_eq!(h1, h2); | ||
| // Case insensitive | ||
| assert_eq!(h1, hash_inibin_key("data", "attackrange")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_hash_lower_accepts_string() { | ||
| let s = String::from("test"); | ||
| assert_eq!(hash_lower(&s), hash_lower("test")); | ||
| assert_eq!(hash_lower(s), hash_lower("test")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_hash_lower_with_delimiter_accepts_string() { | ||
| let a = String::from("DATA"); | ||
| let b = String::from("AttackRange"); | ||
| assert_eq!( | ||
| hash_lower_with_delimiter(&a, &b, '*'), | ||
| hash_lower_with_delimiter("DATA", "AttackRange", '*') | ||
| ); | ||
| assert_eq!( | ||
| hash_lower_with_delimiter(a, b, '*'), | ||
| hash_lower_with_delimiter("DATA", "AttackRange", '*') | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [package] | ||
| name = "ltk_inibin" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| description = "Inibin/Troybin parser for League Toolkit" | ||
| license = "MIT OR Apache-2.0" | ||
| readme = "README.md" | ||
|
|
||
| [dependencies] | ||
| thiserror = { workspace = true } | ||
| byteorder = { workspace = true } | ||
| bitflags = { workspace = true } | ||
| glam = { workspace = true } | ||
| indexmap = { workspace = true } | ||
| ltk_io_ext = { version = "0.4.1", path = "../ltk_io_ext" } | ||
| ltk_hash = { version = "0.2.5", path = "../ltk_hash/" } | ||
|
|
||
| [dev-dependencies] | ||
| approx = { workspace = true } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR description says
ltk_inibinis re-exported throughleague-toolkitbehind theinibinfeature flag, but this change also addsinibinto thedefaultfeature set. That makes the dependency enabled by default for allleague-toolkitusers. Either remove it fromdefault(keeping it truly opt-in) or update the PR/docs to reflect that it’s now part of the default feature set.