diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock index 3b21c76..4ea5290 100644 --- a/.rubocop_gradual.lock +++ b/.rubocop_gradual.lock @@ -11,7 +11,7 @@ "spec/snaky_hash/bad_snake_spec.rb:3931746112": [ [3, 16, 11, "RSpec/DescribeClass: The first argument to describe should be the class or module being tested.", 1577626599] ], - "spec/snaky_hash/snake_spec.rb:3264128361": [ + "spec/snaky_hash/snake_spec.rb:1921604918": [ [4, 3, 92, "RSpec/LeakyConstantDeclaration: Stub class constant instead of declaring explicitly.", 3047242215] ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d9e5d1..25082fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,21 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2. ## [Unreleased] ### Added +### Changed +### Fixed +### Removed + +## [2.0.2] - 2025-05-21 ([tag][2.0.2t]) +### Added - Gem is signed by 20-year cert (@pboling) - Expires 2045-04-29 - Gemspec metadata updates (@pboling) - Documentation updates (@pboling) - CI covering all code, and all key versions of runtime dependencies (@pboling) + - Including Hashie v0, v1, v2, v3, v4, v5, and HEAD - [gh2](https://github.com/oauth-xx/snaky_hash/pull/2) - Serializer option (@pboling) -### Changed -### Fixed -### Removed +- [gh3](https://github.com/oauth-xx/snaky_hash/pull/3) - Serializer Extensions (@pboling) +- Documentation site at [snaky-hash.galtzo.com](https://snaky-hash.galtzo.com) ## [2.0.1] - 2022-09-23 ([tag][2.0.1t]) ### Added @@ -48,7 +54,9 @@ end ### Added - Initial release -[Unreleased]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...main +[Unreleased]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.2...main +[2.0.21]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...v2.0.2 +[2.0.2t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.2 [2.0.1]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.0...v2.0.1 [2.0.1t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.1 [2.0.0]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v1.0.1...v2.0.0 diff --git a/README.md b/README.md index b6bb84b..28f1241 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SnakyHash +# 🐍 SnakyHash [![Version][👽versioni]][👽version] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![Depfu][🔑depfui♻️]][🔑depfu] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi♻️]][🔑qlty-cov] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] @@ -6,14 +6,17 @@ [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon] -This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails. +This library is similar in purpose to the HashWithIndifferentAccess that is famously used in Rails, but does a lot more. -This gem is used by `oauth`, `oauth2`, and other, gems to normalize hash keys to `snake_case` and lookups, +This gem is used by `oauth` and `oauth2` gems to normalize hash keys to `snake_case` and lookups, and provide a nice psuedo-object interface. -It can be thought of as a mashup, with upgrades, to the `Rash` (specifically the [`rash_alt`](https://github.com/shishi/rash_alt) flavor), which is a special `Mash`, made popular by the `hashie` gem, and the `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie). +It can be thought of as a mashup of: -Classes that include `SnakyHash::Snake` should inherit from `Hashie::Mash`. +* `Rash` (specifically the [`rash_alt`](https://github.com/shishi/rash_alt) flavor), which is a special `Mash`, made popular by the `hashie` gem, and +* `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie) + +Classes that `include SnakyHash::Snake.new` should inherit from `Hashie::Mash`. ## New for v2.0.2: Serialization Support @@ -29,6 +32,8 @@ class MyStringKeyedHash < Hashie::Mash end ``` +✨ Also new dump & load plugin extensions to control the way your data is dumped and loaded. + | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions | |-----------------------------------------------|-------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------| | 🧪 [oauth-xx/snaky_hash on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜wiki] | 🏀 Tiny Matrix | ➖ | @@ -147,7 +152,8 @@ end snake = MySnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers") snake.a # => 'a' snake.b # => 'b' -snake[2] # 2 +snake[2] # => 2 +snake["2"] # => nil, note that this gem only affects string / symbol keys. snake.very_fine_hat # => 'Feathers' snake[:very_fine_hat] # => 'Feathers' snake["very_fine_hat"] # => 'Feathers' @@ -158,11 +164,97 @@ The `key_type` determines how the key is actually stored, but the hash acts as " Note also that keys which do not respond to `to_sym`, because they don't have a natural conversion to a Symbol, are left as-is. +### Serialization + +```ruby +class MySerializedSnakedHash < Hashie::Mash + include SnakyHash::Snake.new( + key_type: :symbol, # default :string + serializer: true, # default: false + ) +end + +snake = MySerializedSnakedHash.new(:a => "a", "b" => "b", 2 => 2, "VeryFineHat" => "Feathers") # => {a: "a", b: "b", 2 => 2, very_fine_hat: "Feathers"} +dump = MySerializedSnakedHash.dump(snake) # => "{\"a\":\"a\",\"b\":\"b\",\"2\":2,\"very_fine_hat\":\"Feathers\"}" +hydrated = MySerializedSnakedHash.load(dump) # => {a: "a", b: "b", "2": 2, very_fine_hat: "Feathers"} +hydrated.class # => MySerializedSnakedHash +hydrated.a # => 'a' +hydrated.b # => 'b' +hydrated[2] # => nil # NOTE: this is the opposite of snake[2] => 2 +hydrated["2"] # => 2 # NOTE: this is the opposite of snake["2"] => nil +hydrated.very_fine_hat # => 'Feathers' +hydrated[:very_fine_hat] # => 'Feathers' +hydrated["very_fine_hat"] # => 'Feathers' +``` + +Note that the key `VeryFineHat` changed to `very_fine_hat`. +That is indeed the point of this library, so not a bug. + +Note that the key `2` changed to `"2"` (because JSON keys are strings). +When the JSON dump was reloaded it did not know to restore it as `2` instead of `"2"`. +This is also not a bug, though if you need different behavior, there is a solution in the [next section](#extensions). + +### Extensions + +You can write your own arbitrary extensions: + +* "Hash Load" extensions operate on the hash, and nested hashes + * use `::load_hash_extensions.add(:extension_name) { |hash| }` +* "Load" extensions operate on the values, and nested hash's values, if any + * use `::load_extensions.add(:extension_name) { |value| }` +* "Dump" extensions operate on the values, and nested hash's values, if any + * use `::dump_extensions.add(:extension_name) { |value| }` + +#### Example + +Let's say I want all integer-like keys, except 0, to be integer keys, +while 0 converts to, and stays, a string forever. + +```ruby +class MyExtSnakedHash < Hashie::Mash + include SnakyHash::Snake.new( + key_type: :symbol, # default :string + serializer: true, # default: false + ) +end + +MyExtSnakedHash.load_hash_extensions.add(:non_zero_keys_to_int) do |value| + if value.is_a?(Hash) + value.transform_keys do |key| + key_int = key.to_s.to_i + if key_int > 0 + key_int + else + key + end + end + else + value + end +end + +snake = MyExtSnakedHash.new(1 => "a", 0 => 4, "VeryFineHat" => {3 => "v", 5 => 7, :very_fine_hat => "feathers"}) # => {1 => "a", 0 => 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}} +dump = MyExtSnakedHash.dump(snake) # => "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}" +hydrated = MyExtSnakedHash.load(dump) # => {1 => "a", "0": 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}} +hydrated.class # => MyExtSnakedHash +hydrated["1"] # => nil +hydrated[1] # => "a" +hydrated["0"] # => 4 +hydrated[0] # => nil +hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"} +hydrated.very_fine_hat.very_fine_hat # => "feathers" +hydrated.very_fine_hat[:very_fine_hat] # => 'feathers' +hydrated.very_fine_hat["very_fine_hat"] # => 'feathers' +``` + +See the specs for more examples. + ### Stranger Things I don't recommend using these features... but they exist (for now). You can still access the original un-snaked camel keys. And through them you can even use un-snaked camel methods. +But don't. ```ruby snake.key?("VeryFineHat") # => true diff --git a/doc/SnakyHash.html b/doc/SnakyHash.html index f21b4cb..ca07657 100644 --- a/doc/SnakyHash.html +++ b/doc/SnakyHash.html @@ -123,7 +123,7 @@

Overview

diff --git a/doc/SnakyHash/Error.html b/doc/SnakyHash/Error.html index 189a40d..076414f 100644 --- a/doc/SnakyHash/Error.html +++ b/doc/SnakyHash/Error.html @@ -114,7 +114,7 @@ diff --git a/doc/SnakyHash/Extensions.html b/doc/SnakyHash/Extensions.html index 226b2fe..6139613 100644 --- a/doc/SnakyHash/Extensions.html +++ b/doc/SnakyHash/Extensions.html @@ -446,7 +446,7 @@

diff --git a/doc/SnakyHash/Serializer.html b/doc/SnakyHash/Serializer.html index b1da019..1366c77 100644 --- a/doc/SnakyHash/Serializer.html +++ b/doc/SnakyHash/Serializer.html @@ -303,7 +303,7 @@

def load(raw_hash) hash = JSON.parse(presence(raw_hash) || "{}") - hash = load_hash(hash) + hash = load_value(new(hash)) new(hash) end @@ -316,7 +316,7 @@

diff --git a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html index e487fc2..6619ac2 100644 --- a/doc/SnakyHash/Serializer/BackportedInstanceMethods.html +++ b/doc/SnakyHash/Serializer/BackportedInstanceMethods.html @@ -190,7 +190,7 @@

diff --git a/doc/SnakyHash/Serializer/Modulizer.html b/doc/SnakyHash/Serializer/Modulizer.html index 95a03d3..d45638f 100644 --- a/doc/SnakyHash/Serializer/Modulizer.html +++ b/doc/SnakyHash/Serializer/Modulizer.html @@ -189,7 +189,7 @@

diff --git a/doc/SnakyHash/Snake.html b/doc/SnakyHash/Snake.html index 6612c3d..7217710 100644 --- a/doc/SnakyHash/Snake.html +++ b/doc/SnakyHash/Snake.html @@ -275,7 +275,7 @@

diff --git a/doc/SnakyHash/Snake/SnakyModulizer.html b/doc/SnakyHash/Snake/SnakyModulizer.html index 4941442..9a89620 100644 --- a/doc/SnakyHash/Snake/SnakyModulizer.html +++ b/doc/SnakyHash/Snake/SnakyModulizer.html @@ -261,7 +261,7 @@

diff --git a/doc/SnakyHash/StringKeyed.html b/doc/SnakyHash/StringKeyed.html index 864cac8..06df63f 100644 --- a/doc/SnakyHash/StringKeyed.html +++ b/doc/SnakyHash/StringKeyed.html @@ -130,7 +130,7 @@

Overview

diff --git a/doc/SnakyHash/SymbolKeyed.html b/doc/SnakyHash/SymbolKeyed.html index b740b44..262180e 100644 --- a/doc/SnakyHash/SymbolKeyed.html +++ b/doc/SnakyHash/SymbolKeyed.html @@ -130,7 +130,7 @@

Overview

diff --git a/doc/SnakyHash/Version.html b/doc/SnakyHash/Version.html index eba9e80..e180a1c 100644 --- a/doc/SnakyHash/Version.html +++ b/doc/SnakyHash/Version.html @@ -111,7 +111,7 @@

diff --git a/doc/_index.html b/doc/_index.html index 9046fa6..e8edf54 100644 --- a/doc/_index.html +++ b/doc/_index.html @@ -214,7 +214,7 @@

Namespace Listing A-Z

diff --git a/doc/file.CHANGELOG.html b/doc/file.CHANGELOG.html index e53181f..01a3bbf 100644 --- a/doc/file.CHANGELOG.html +++ b/doc/file.CHANGELOG.html @@ -63,8 +63,14 @@

The format (since v2) is based on Keep a Changelog v1,
and this project adheres to Semantic Versioning v2.

-

Unreleased

+

Unreleased

Added

+

Changed

+

Fixed

+

Removed

+ +

[2.0.2] - 2025-05-21 (tag)

+

Added