snaky_hash 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d15358e100316a4b2347ab0b1a55b2e16f87ee69991b0579328b049b8b04abf
4
- data.tar.gz: 5030164f28379f7d237da28aab846aa9ce1cfb55f308e257bb3cb38246da1f36
3
+ metadata.gz: 6994e51a3a7343a5b2b2b1d0d055721203ed81764991032a0f81ae6c2931646f
4
+ data.tar.gz: 8453695e75b616782ab21c4cef71fc3870c1a1984b7c7f477babde05c2eef252
5
5
  SHA512:
6
- metadata.gz: d34d715bf7deb832a4ce4f04338d3cff147547d979bc42d56e8258a0975265296ec5550035581181e94d2a9b7af1afbf9de27db476b8229cec5d5c18e43b51e7
7
- data.tar.gz: fa3e81dd83544e92d1bfe7174e34d8d6cec4d4f20d39d84bc82bf16b9852155e962d58058ff7aa312f32f3d97a7d3857367aa61892912811bd25de94982ab825
6
+ metadata.gz: 0516c0b68ff581232463f5f6d81802fe3f2314693ce9270ce56d677bf62090163b5663bbb4a4e1d8ede5c26d9c51404cc1fa03005509a399b17e98cc4536aea2
7
+ data.tar.gz: 26816588362b496f9753f8087aabf650a8eaf024038e8f55e5216ad112d2b2c61f1f8ebcc225e184618e822c62a3e9f183bd4635c5c76ce8039bc209e922d860
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,20 +1,77 @@
1
1
  # Changelog
2
+
2
3
  All notable changes to this project will be documented in this file.
3
4
 
4
5
  The format (since v2) is based on [Keep a Changelog v1](https://keepachangelog.com/en/1.0.0/),
5
6
  and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.0.0.html).
6
7
 
7
8
  ## [Unreleased]
9
+
8
10
  ### Added
11
+
9
12
  ### Changed
10
- ### Fixed
13
+
14
+ ### Deprecated
15
+
11
16
  ### Removed
12
17
 
13
- ## [2.0.2] - 2025-05-21 ([tag][2.0.2t])
18
+ ### Fixed
19
+
20
+ ### Security
21
+
22
+ ## [2.0.4] - 2026-05-16
23
+
24
+ - TAG: [v2.0.4][2.0.4t]
25
+ - COVERAGE: 100.00% -- 133/133 lines in 7 files
26
+ - BRANCH COVERAGE: 100.00% -- 38/38 branches in 7 files
27
+ - 100.00% documented
28
+
29
+ ### Added
30
+
31
+ - Incident Response Plan in IRP.md
32
+ - SnakyHash::VERSION (Traditional Constant Location)
33
+ - kettle-dev & kettle-test dev harnesses
34
+
35
+ ### Changed
36
+
37
+ - Contributor Conenant updated to version 2.1
38
+
39
+ ### Fixed
40
+
41
+ - Specs
42
+
43
+ ## [2.0.3] - 2025-05-23
44
+
45
+ - TAG: [v2.0.3][2.0.3t]
46
+ - COVERAGE: 100.00% -- 132/132 lines in 7 files
47
+ - BRANCH COVERAGE: 100.00% -- 38/38 branches in 7 files
48
+ - 100.00% documented
49
+
50
+ ### Added
51
+
52
+ - `#dump` instance method injected by `extend SnakyHash::Serializer` (@pboling)
53
+ - `dump_hash_extensions` - new feature, analogous to `load_hash_extensions` (@pboling)
54
+ - `dump_value_extensions` - alternate name for `dump_extensions` (@pboling)
55
+ - `load_value_extensions` - alternate name for `load_extensions` (@pboling)
56
+ - Clarifying documentation (@pboling)
57
+
58
+ ### Fixed
59
+
60
+ - [gh4](https://github.com/oauth-xx/snaky_hash/pull/4) - Serializer extensions dump and load empty values properly (@pboling)
61
+ - Fixed `dump_extensions`, `load_extensions`, `load_hash_extensions`
62
+ - Intended usage is primarily JSON, and oauth2 gem
63
+ - OAuth2 spec can have legitimately empty values (e.g. scopes could be empty)
64
+ - Previous logic was inherited from design decisions made by `serialized_hashie` gem; doesn't apply here
65
+
66
+ ## [2.0.2] - 2025-05-21
67
+
68
+ - TAG: [v2.0.2][2.0.2t]
14
69
  - COVERAGE: 100.00% -- 119/119 lines in 7 files
15
70
  - BRANCH COVERAGE: 100.00% -- 35/35 branches in 7 files
16
71
  - 100.00% documented
72
+
17
73
  ### Added
74
+
18
75
  - Gem is signed by 20-year cert (@pboling)
19
76
  - Expires 2045-04-29
20
77
  - Gemspec metadata updates (@pboling)
@@ -26,40 +83,66 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.
26
83
  - Documentation site at [snaky-hash.galtzo.com](https://snaky-hash.galtzo.com) (@pboling)
27
84
  - 100% documented! (@pboling)
28
85
 
29
- ## [2.0.1] - 2022-09-23 ([tag][2.0.1t])
86
+ ## [2.0.1] - 2022-09-23
87
+
88
+ - TAG: [v2.0.1][2.0.1t]
89
+
30
90
  ### Added
91
+
31
92
  - Certificate for signing gem releases (@pboling)
32
93
  - Gemspec metadata (@pboling)
33
94
  - funding_uri
34
95
  - mailing_list_uri
35
96
  - Checksums for released gems (@pboling)
97
+
36
98
  ### Changed
99
+
37
100
  - Gem releases are now cryptographically signed (@pboling)
38
101
 
39
- ## [2.0.0] - 2022-08-29 ([tag][2.0.0t])
102
+ ## [2.0.0] - 2022-08-29
103
+
104
+ - TAG: [v2.0.0][2.0.0t]
105
+
40
106
  ### Changed
107
+
41
108
  - **BREAKING**: `SnakeHash::Snake` is now a mixin, now with support for symbol or string keys
42
109
  ```ruby
43
110
  class MySnakedHash < Hashie::Mash
44
111
  include SnakyHash::Snake.new(key_type: :string) # or :symbol
45
112
  end
46
113
  ```
114
+
47
115
  ### Added
116
+
48
117
  - `SnakyHash::StringKeyed`: a Hashie::Mash class with snake-cased String keys
49
118
  - `SnakyHash::SymbolKeyed`: a Hashie::Mash class with snake-cased Symbol keys
50
119
 
51
- ## [1.0.1] - 2022-08-26 ([tag][1.0.1t])
120
+ ## [1.0.1] - 2022-08-26
121
+
122
+ - TAG: [v1.0.1][1.0.1t]
123
+
52
124
  ### Added
125
+
53
126
  - Missing LICENSE.txt file to release
127
+
54
128
  ### Removed
129
+
55
130
  - Accidentally added bundler dependency (vestige of transpec process) is now removed
56
131
 
57
- ## [1.0.0] - 2022-08-26 ([tag][1.0.0t])
132
+ ## [1.0.0] - 2022-08-26
133
+
134
+ - TAG: [v1.0.0][1.0.0t]
135
+
58
136
  ### Added
137
+
59
138
  - Initial release
60
139
 
61
- [Unreleased]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.2...main
62
- [2.0.21]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...v2.0.2
140
+ [Unreleased]: https://github.com/ruby-oauth/snaky_hash/compare/v2.0.4...HEAD
141
+ [2.0.4]: https://github.com/ruby-oauth/snaky_hash/compare/v2.0.3...v2.0.4
142
+ [2.0.4t]: https://github.com/ruby-oauth/snaky_hash/releases/tag/v2.0.4
143
+ [2.0.3]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.2...v2.0.3
144
+ [2.0.3t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.3
145
+ [2.0.2]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.1...v2.0.2
63
146
  [2.0.2t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.2
64
147
  [2.0.1]: https://gitlab.com/oauth-xx/snaky_hash/-/compare/v2.0.0...v2.0.1
65
148
  [2.0.1t]: https://gitlab.com/oauth-xx/snaky_hash/-/releases/tag/v2.0.1
data/CITATION.cff ADDED
@@ -0,0 +1,23 @@
1
+ cff-version: 1.2.0
2
+ title: snaky_hash
3
+ message: >-
4
+ If you use this work and you want to cite it,
5
+ then you can use the metadata from this file.
6
+ type: software
7
+ authors:
8
+ - given-names: Peter Hurn
9
+ family-names: Boling
10
+ email: peter@railsbling.com
11
+ affiliation: railsbling.com
12
+ orcid: 'https://orcid.org/0009-0008-8519-441X'
13
+ - given-names: Aboling0
14
+ email: aboling@railsbling.com
15
+ affiliation: railsbling.com
16
+ identifiers:
17
+ - type: url
18
+ value: 'https://github.com/oauth-xx/snaky_hash'
19
+ description: snaky_hash
20
+ repository-code: 'https://github.com/oauth-xx/snaky_hash'
21
+ abstract: >-
22
+ snaky_hash
23
+ license: See license file
data/CODE_OF_CONDUCT.md CHANGED
@@ -7,8 +7,8 @@ We as members, contributors, and leaders pledge to make participation in our
7
7
  community a harassment-free experience for everyone, regardless of age, body
8
8
  size, visible or invisible disability, ethnicity, sex characteristics, gender
9
9
  identity and expression, level of experience, education, socio-economic status,
10
- nationality, personal appearance, race, religion, or sexual identity
11
- and orientation.
10
+ nationality, personal appearance, race, caste, color, religion, or sexual
11
+ identity and orientation.
12
12
 
13
13
  We pledge to act and interact in ways that contribute to an open, welcoming,
14
14
  diverse, inclusive, and healthy community.
@@ -23,17 +23,17 @@ community include:
23
23
  * Giving and gracefully accepting constructive feedback
24
24
  * Accepting responsibility and apologizing to those affected by our mistakes,
25
25
  and learning from the experience
26
- * Focusing on what is best not just for us as individuals, but for the
27
- overall community
26
+ * Focusing on what is best not just for us as individuals, but for the overall
27
+ community
28
28
 
29
29
  Examples of unacceptable behavior include:
30
30
 
31
- * The use of sexualized language or imagery, and sexual attention or
32
- advances of any kind
31
+ * The use of sexualized language or imagery, and sexual attention or advances of
32
+ any kind
33
33
  * Trolling, insulting or derogatory comments, and personal or political attacks
34
34
  * Public or private harassment
35
- * Publishing others' private information, such as a physical or email
36
- address, without their explicit permission
35
+ * Publishing others' private information, such as a physical or email address,
36
+ without their explicit permission
37
37
  * Other conduct which could reasonably be considered inappropriate in a
38
38
  professional setting
39
39
 
@@ -53,7 +53,7 @@ decisions when appropriate.
53
53
 
54
54
  This Code of Conduct applies within all community spaces, and also applies when
55
55
  an individual is officially representing the community in public spaces.
56
- Examples of representing our community include using an official e-mail address,
56
+ Examples of representing our community include using an official email address,
57
57
  posting via an official social media account, or acting as an appointed
58
58
  representative at an online or offline event.
59
59
 
@@ -61,7 +61,7 @@ representative at an online or offline event.
61
61
 
62
62
  Instances of abusive, harassing, or otherwise unacceptable behavior may be
63
63
  reported to the community leaders responsible for enforcement at
64
- [INSERT CONTACT METHOD].
64
+ [![Contact BDFL][🚂bdfl-contact-img]][🚂bdfl-contact].
65
65
  All complaints will be reviewed and investigated promptly and fairly.
66
66
 
67
67
  All community leaders are obligated to respect the privacy and security of the
@@ -83,15 +83,15 @@ behavior was inappropriate. A public apology may be requested.
83
83
 
84
84
  ### 2. Warning
85
85
 
86
- **Community Impact**: A violation through a single incident or series
87
- of actions.
86
+ **Community Impact**: A violation through a single incident or series of
87
+ actions.
88
88
 
89
89
  **Consequence**: A warning with consequences for continued behavior. No
90
90
  interaction with the people involved, including unsolicited interaction with
91
91
  those enforcing the Code of Conduct, for a specified period of time. This
92
92
  includes avoiding interactions in community spaces as well as external channels
93
- like social media. Violating these terms may lead to a temporary or
94
- permanent ban.
93
+ like social media. Violating these terms may lead to a temporary or permanent
94
+ ban.
95
95
 
96
96
  ### 3. Temporary Ban
97
97
 
@@ -107,27 +107,29 @@ Violating these terms may lead to a permanent ban.
107
107
  ### 4. Permanent Ban
108
108
 
109
109
  **Community Impact**: Demonstrating a pattern of violation of community
110
- standards, including sustained inappropriate behavior, harassment of an
110
+ standards, including sustained inappropriate behavior, harassment of an
111
111
  individual, or aggression toward or disparagement of classes of individuals.
112
112
 
113
- **Consequence**: A permanent ban from any sort of public interaction within
114
- the community.
113
+ **Consequence**: A permanent ban from any sort of public interaction within the
114
+ community.
115
115
 
116
116
  ## Attribution
117
117
 
118
118
  This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119
- version 2.0, available at
120
- [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
119
+ version 2.1, available at
120
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
121
121
 
122
122
  Community Impact Guidelines were inspired by
123
123
  [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124
124
 
125
125
  For answers to common questions about this code of conduct, see the FAQ at
126
- [https://www.contributor-covenant.org/faq][FAQ]. Translations are available
127
- at [https://www.contributor-covenant.org/translations][translations].
126
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
127
+ [https://www.contributor-covenant.org/translations][translations].
128
128
 
129
129
  [homepage]: https://www.contributor-covenant.org
130
- [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
130
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
131
131
  [Mozilla CoC]: https://github.com/mozilla/diversity
132
132
  [FAQ]: https://www.contributor-covenant.org/faq
133
133
  [translations]: https://www.contributor-covenant.org/translations
134
+ [🚂bdfl-contact]: http://www.railsbling.com/contact
135
+ [🚂bdfl-contact-img]: https://img.shields.io/badge/Contact-BDFL-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
data/IRP.md ADDED
@@ -0,0 +1,107 @@
1
+ # Incident Response Plan (IRP)
2
+
3
+ Status: Draft
4
+
5
+ ## Purpose
6
+
7
+ This Incident Response Plan (IRP) defines the steps the project maintainer(s) will follow when handling security incidents related to the `snaky_hash` gem. It is written for a small project with a single primary maintainer and is intended to be practical, concise, and actionable.
8
+
9
+ ## Scope
10
+
11
+ Applies to security incidents that affect the `snaky_hash` codebase, releases (gems), CI/CD infrastructure related to building and publishing the gem, repository credentials, or any compromise of project infrastructure that could impact users.
12
+
13
+ ## Key assumptions
14
+ - This project is maintained primarily by a single maintainer.
15
+ - Public vulnerability disclosure is handled via Tidelift (see `SECURITY.md`).
16
+ - The maintainer will act as incident commander unless otherwise delegated.
17
+
18
+ ## Contact & Roles
19
+
20
+ - Incident Commander: Primary maintainer (repo owner). Responsible for coordinating triage, remediation, and communications.
21
+ - Secondary Contact: (optional) A trusted collaborator or organization contact if available.
22
+
23
+ ### If you are an external reporter
24
+ - Do not publicly disclose details of an active vulnerability before coordination via Tidelift.
25
+ - See `SECURITY.md` for Tidelift disclosure instructions. If the reporter has questions and cannot use Tidelift, they may open a direct encrypted report as described in `SECURITY.md` (if available) or email the maintainer contact listed in the repository.
26
+
27
+ ## Incident Handling Workflow (high level)
28
+ 1. Identification & Reporting
29
+ - Reports may arrive via Tidelift, issue tracker, direct email, or third-party advisories.
30
+ - Immediately acknowledge receipt (within 24-72 hours) via the reporting channel.
31
+
32
+ 2. Triage & Initial Assessment (first 72 hours)
33
+ - Confirm the report is not duplicative and gather: reproducer, affected versions, attack surface, exploitability, and CVSS-like severity estimate.
34
+ - Verify the issue against the codebase and reproduce locally if possible.
35
+ - Determine scope: which versions are affected, whether the issue is in code paths executed in common setups, and whether a workaround exists.
36
+
37
+ 3. Containment & Mitigation
38
+ - If a simple mitigation or workaround (configuration change, safe default, or recommended upgrade) exists, document it clearly in the issue/Tidelift advisory.
39
+ - If immediate removal of a release is required (rare), consult Tidelift for coordinated takedown and notify package hosts if applicable.
40
+
41
+ 4. Remediation & Patch
42
+ - Prepare a fix in a branch with tests and changelog entries. Prefer minimal, well-tested changes.
43
+ - Include tests that reproduce the faulty behavior and demonstrate the fix.
44
+ - Hardening: add fuzz tests, input validation, or additional checks as appropriate.
45
+
46
+ 5. Release & Disclosure
47
+ - Coordinate disclosure through Tidelift per `SECURITY.md` timelines. Aim for a coordinated disclosure and patch release to minimize risk to users.
48
+ - Publish a patch release (increment gem version) and an advisory via Tidelift.
49
+ - Update `CHANGELOG.md` and repository release notes with non-sensitive details.
50
+
51
+ 6. Post-Incident
52
+ - Produce a short postmortem: timeline, root cause, actions taken, and follow-ups.
53
+ - Add/adjust tests and CI checks to prevent regressions.
54
+ - If credentials or infrastructure were compromised, rotate secrets and audit access.
55
+
56
+ ## Severity classification (guidance)
57
+ - High/Critical: Remote code execution, data exfiltration, or any vulnerability that can be exploited without user interaction. Immediate action and prioritized patching.
58
+ - Medium: Privilege escalation, sensitive information leaks that require specific conditions. Patch in the next release cycle with advisory.
59
+ - Low: Minor information leaks, UI issues, or non-exploitable bugs. Fix normally and include in the next scheduled release.
60
+
61
+ ## Preservation of evidence
62
+ - Preserve all reporter-provided data, logs, and reproducer code in a secure location (local encrypted storage or private branch) for the investigation.
63
+ - Do not publish evidence that would enable exploitation before coordinated disclosure.
64
+
65
+ ## Communication templates
66
+ Acknowledgement (to reporter)
67
+
68
+ "Thank you for reporting this issue. I've received your report and will triage it within 72 hours. If you can, please provide reproduction steps, affected versions, and any exploit PoC. I will coordinate disclosure through Tidelift per the project's security policy."
69
+
70
+ Public advisory (after patch is ready)
71
+
72
+ "A security advisory for snaky_hash (versions X.Y.Z) has been published via Tidelift. Please upgrade to version A.B.C which patches [brief description]. See the advisory for details and recommended mitigations."
73
+
74
+ ## Runbook: Quick steps for a maintainer to patch and release
75
+ 1. Create a branch: `git checkout -b fix/security-brief-description`
76
+ 2. Reproduce the issue locally and add a regression spec in `spec/`.
77
+ 3. Implement the fix and run the test suite: `bundle exec rspec` (or the project's preferred test command).
78
+ 4. Bump version in `lib/snaky_hash/version.rb` following semantic versioning.
79
+ 5. Update `CHANGELOG.md` with an entry describing the fix (avoid exploit details).
80
+ 6. Commit and push the branch, open a PR, and merge after approvals.
81
+ 7. Build and push the gem: `gem build snaky_hash.gemspec && gem push pkg/...` (coordinate with Tidelift before public push if disclosure is coordinated).
82
+ 8. Publish a release on GitHub and ensure the Tidelift advisory is posted.
83
+
84
+ ## Operational notes
85
+ - Secrets: Use local encrypted storage for any sensitive reporter data. If repository or CI secrets may be compromised, rotate them immediately and update dependent services.
86
+ - Access control: Limit who can publish gems and who has admin access to the repo. Keep an up-to-date list of collaborators in a secure place.
87
+
88
+ ## Legal & regulatory
89
+ - If the incident involves user data or has legal implications, consult legal counsel or the maintainers' employer as appropriate. The maintainer should document the timeline and all communications.
90
+
91
+ ## Retrospective & continuous improvement
92
+ After an incident, perform a brief post-incident review covering:
93
+ - What happened and why
94
+ - What was done to contain and remediate
95
+ - What tests or process changes will prevent recurrence
96
+ - Assign owners and deadlines for follow-up tasks
97
+
98
+ ## References
99
+ - See `SECURITY.md` for the project's official disclosure channel (Tidelift).
100
+
101
+ ## Appendix: Example checklist for an incident
102
+ - [ ] Acknowledge report to reporter (24-72 hours)
103
+ - [ ] Reproduce and classify severity
104
+ - [ ] Prepare and test a fix in a branch
105
+ - [ ] Coordinate disclosure via Tidelift
106
+ - [ ] Publish patch release and advisory
107
+ - [ ] Postmortem and follow-up actions
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2022, 2025 Peter Boling
3
+ Copyright (c) 2022, 2025-2026 Peter Boling
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🐍 SnakyHash
2
2
 
3
- [![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]
3
+ [![Version][👽versioni]][👽version] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi♻️]][🔑qlty-cov] [![QLTY Maintainability][🔑qlty-mnti♻️]][🔑qlty-mnt] [![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]
4
4
 
5
5
  ---
6
6
 
@@ -14,7 +14,7 @@ and provide a nice psuedo-object interface.
14
14
  It can be thought of as a mashup of:
15
15
 
16
16
  * `Rash` (specifically the [`rash_alt`](https://github.com/shishi/rash_alt) flavor), which is a special `Mash`, made popular by the `hashie` gem, and
17
- * `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie)
17
+ * `serialized_hashie` [gem by krystal](https://github.com/krystal/serialized-hashie), rewritten, with some behavior changes
18
18
 
19
19
  Classes that `include SnakyHash::Snake.new` should inherit from `Hashie::Mash`.
20
20
 
@@ -46,6 +46,17 @@ SnakyHash::StringKeyed.class_eval do
46
46
  end
47
47
  ```
48
48
 
49
+ or you can create a custom class
50
+
51
+ ```ruby
52
+ class MyHash < Hashie::Mash
53
+ include SnakyHash::Snake.new(key_type: :string, serializer: true)
54
+ # Which is the same as:
55
+ # include SnakyHash::Snake.new(key_type: :string)
56
+ # extend SnakyHash::Serializer
57
+ end
58
+ ```
59
+
49
60
  You can then add serialization extensions as needed. See [serialization](#serialization) and [extensions](#extensions) for more.
50
61
 
51
62
  | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
@@ -108,7 +119,7 @@ and are developed in tight collaboration with the oauth and oauth2 gems.
108
119
  | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
109
120
  | Works with MRI Ruby 2 | [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] |
110
121
  | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
111
- | Documentation | [![Discussion][⛳gg-discussions-img]][⛳gg-discussions] [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![HEAD on RubyDoc.info][📜docs-head-rd-img]][🚎yard-head] [![BDFL Blog][🚂bdfl-blog-img]][🚂bdfl-blog] [![Wiki][📜wiki-img]][📜wiki] |
122
+ | Documentation | [![Discussion][⛳gg-discussions-img]][⛳gg-discussions] [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![BDFL Blog][🚂bdfl-blog-img]][🚂bdfl-blog] [![Wiki][📜wiki-img]][📜wiki] |
112
123
  | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
113
124
  | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] |
114
125
  | Support | [![Live Chat on Discord][✉️discord-invite-img]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
@@ -212,71 +223,95 @@ This is also not a bug, though if you need different behavior, there is a soluti
212
223
 
213
224
  You can write your own arbitrary extensions:
214
225
 
215
- * "Hash Load" extensions operate on the hash, and nested hashes
226
+ * "Hash Load" extensions operate on the hash and nested hashes
216
227
  * use `::load_hash_extensions.add(:extension_name) { |hash| }`
217
- * "Load" extensions operate on the values, and nested hash's values, if any
218
- * use `::load_extensions.add(:extension_name) { |value| }`
219
- * "Dump" extensions operate on the values, and nested hash's values, if any
220
- * use `::dump_extensions.add(:extension_name) { |value| }`
228
+ * since v2.0.2, bugs fixed in v2.0.3
229
+ * "Value Load" extensions operate on the values, and nested hashes' values, if any
230
+ * use `::load_value_extensions.add(:extension_name) { |value| }`
231
+ * since v2.0.2, bugs fixed in v2.0.3
232
+ * "Hash Dump" extensions operate on the hash and nested hashes
233
+ * use `::dump_hash_extensions.add(:extension_name) { |value| }`
234
+ * since v2.0.3
235
+ * "Value Dump" extensions operate on the values, and nested hashes' values, if any
236
+ * use `::dump_value_extensions.add(:extension_name) { |value| }`
237
+ * since v2.0.2, bugs fixed in v2.0.3
221
238
 
222
239
  #### Example
223
240
 
224
- Let's say I want all integer-like keys, except 0, to be integer keys,
225
- while 0 converts to, and stays, a string forever.
241
+ Let's say I want to really smash up my hash and make it more food-like.
226
242
 
227
243
  ```ruby
228
244
  class MyExtSnakedHash < Hashie::Mash
229
245
  include SnakyHash::Snake.new(
230
246
  key_type: :symbol, # default :string
231
- serializer: true, # default: false
247
+ serializer: true, # default: false
232
248
  )
233
249
  end
234
250
 
235
- MyExtSnakedHash.load_hash_extensions.add(:non_zero_keys_to_int) do |value|
236
- if value.is_a?(Hash)
237
- value.transform_keys do |key|
238
- key_int = key.to_s.to_i
239
- if key_int > 0
240
- key_int
241
- else
242
- key
243
- end
244
- end
245
- else
246
- value
251
+ # We could swap all values with indexed apples (obliteraating nested data!)
252
+ MyExtSnakedHash.dump_hash_extensions.add(:to_apple) do |value|
253
+ num = 0
254
+ value.transform_values do |_key|
255
+ key = "apple-#{num}"
256
+ num += 1
257
+ key
247
258
  end
248
259
  end
249
260
 
250
- 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"}}
251
- dump = MyExtSnakedHash.dump(snake) # => "{\"1\":\"a\",\"0\":4,\"very_fine_hat\":{\"3\":\"v\",\"5\":7,\"very_fine_hat\":\"feathers\"}}"
252
- hydrated = MyExtSnakedHash.load(dump) # => {1 => "a", "0": 4, very_fine_hat: {3 => "v", 5 => 7, very_fine_hat: "feathers"}}
253
- hydrated.class # => MyExtSnakedHash
254
- hydrated["1"] # => nil
255
- hydrated[1] # => "a"
256
- hydrated["0"] # => 4
257
- hydrated[0] # => nil
258
- hydrated.very_fine_hat # => {3 => "v", 5 => 7, very_fine_hat: "feathers"}
259
- hydrated.very_fine_hat.very_fine_hat # => "feathers"
260
- hydrated.very_fine_hat[:very_fine_hat] # => 'feathers'
261
- hydrated.very_fine_hat["very_fine_hat"] # => 'feathers'
261
+ # And then when loading the dump we could convert the yum to pear
262
+ MyExtSnakedHash.load_hash_extensions.add(:apple_to_pear) do |value|
263
+ value.transform_keys do |key|
264
+ key.to_s.sub("yum", "pear")
265
+ end
266
+ end
267
+
268
+ # We could swap all index numbers "beet-<number>"
269
+ MyExtSnakedHash.dump_value_extensions.add(:to_beet) do |value|
270
+ value.to_s.sub(/(\d+)/) { |match| "beet-#{match[0]}" }
271
+ end
272
+
273
+ # And then when loading the dump we could convert beet to corn
274
+ MyExtSnakedHash.load_value_extensions.add(:beet_to_corn) do |value|
275
+ value.to_s.sub("beet", "corn")
276
+ end
277
+
278
+ snake = MyExtSnakedHash.new({"YumBread" => "b", "YumCake" => {"b" => "b"}, "YumBoba" => [1, 2, 3]})
279
+ snake # => {yum_bread: "b", yum_cake: {b: "b"}, yum_boba: [1, 2, 3]}
280
+ snake.yum_bread # => "b"
281
+ snake.yum_cake # => {b: "b"}
282
+ snake.yum_boba # => [1, 2, 3]
283
+ dump = snake.dump
284
+ dump # => "{\"yum_bread\":\"apple-beet-0\",\"yum_cake\":\"apple-beet-1\",\"yum_boba\":\"apple-beet-2\"}"
285
+ hydrated = MyExtSnakedHash.load(dump)
286
+ hydrated # => {pear_bread: "apple-corn-0", pear_cake: "apple-corn-1", pear_boba: "apple-corn-2"}
262
287
  ```
263
288
 
264
289
  See the specs for more examples.
265
290
 
266
- ### Stranger Things
291
+ ### Bad Ideas
267
292
 
268
293
  I don't recommend using these features... but they exist (for now).
294
+
295
+ <details>
296
+ <summary>Show me what I should *not* do!</summary>
297
+
269
298
  You can still access the original un-snaked camel keys.
270
299
  And through them you can even use un-snaked camel methods.
271
300
  But don't.
272
301
 
273
302
  ```ruby
303
+ snake = SnakyHash::StringKeyed["VeryFineHat" => "Feathers"]
274
304
  snake.key?("VeryFineHat") # => true
275
305
  snake["VeryFineHat"] # => 'Feathers'
276
306
  snake.VeryFineHat # => 'Feathers', PLEASE don't do this!!!
277
307
  snake["VeryFineHat"] = "pop" # Please don't do this... you'll get a warning, and it works (for now), but no guarantees.
278
308
  # WARN -- : You are setting a key that conflicts with a built-in method MySnakedHash#VeryFineHat defined in MySnakedHash. This can cause unexpected behavior when accessing the key as a property. You can still access the key via the #[] method.
279
309
  # => "pop"
310
+ ```
311
+
312
+ Since you are reading this, here's what to do instead.
313
+
314
+ ```ruby
280
315
  snake.very_fine_hat = "pop" # => 'pop', do this instead!!!
281
316
  snake.very_fine_hat # => 'pop'
282
317
  snake[:very_fine_hat] = "moose" # => 'moose', or do this instead!!!
@@ -285,6 +320,8 @@ snake["very_fine_hat"] = "cheese" # => 'cheese', or do this instead!!!
285
320
  snake.very_fine_hat # => 'cheese'
286
321
  ```
287
322
 
323
+ </details>
324
+
288
325
  ### 🚀 Release Instructions
289
326
 
290
327
  See [CONTRIBUTING.md][🤝contributing].
@@ -310,8 +347,8 @@ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
310
347
 
311
348
  ### 🪇 Code of Conduct
312
349
 
313
- Everyone interacting in this project's codebases, issue trackers,
314
- chat rooms and mailing lists is expected to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
350
+ Everyone interacting with this project's codebases, issue trackers,
351
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
315
352
 
316
353
  ## 🌈 Contributors
317
354
 
@@ -378,7 +415,7 @@ See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright
378
415
 
379
416
  <ul>
380
417
  <li>
381
- 2022, 2025 Peter H. Boling, of
418
+ 2022, 2025-2026 Peter H. Boling, of
382
419
  <a href="https://railsbling.com">
383
420
  RailsBling.com
384
421
  <picture>
@@ -452,8 +489,6 @@ or one of the others at the head of this README.
452
489
  [📜wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=Wiki&logoColor=white
453
490
  [👽dl-rank]: https://rubygems.org/gems/snaky_hash
454
491
  [👽dl-ranki]: https://img.shields.io/gem/rd/snaky_hash.svg
455
- [👽oss-help]: https://www.codetriage.com/oauth-xx/snaky_hash
456
- [👽oss-helpi]: https://www.codetriage.com/oauth-xx/snaky_hash/badges/users.svg
457
492
  [👽version]: https://rubygems.org/gems/snaky_hash
458
493
  [👽versioni]: https://img.shields.io/gem/v/snaky_hash.svg
459
494
  [🔑qlty-mnt]: https://qlty.sh/gh/oauth-xx/projects/snaky_hash
@@ -464,8 +499,6 @@ or one of the others at the head of this README.
464
499
  [🔑codecovi♻️]: https://codecov.io/gh/oauth-xx/snaky_hash/graph/badge.svg?token=XqaZixl4ss
465
500
  [🔑coveralls]: https://coveralls.io/github/oauth-xx/snaky_hash?branch=main
466
501
  [🔑coveralls-img]: https://coveralls.io/repos/github/oauth-xx/snaky_hash/badge.svg?branch=main
467
- [🔑depfu]: https://depfu.com/github/oauth-xx/snaky_hash?project_id=63073
468
- [🔑depfui♻️]: https://badges.depfu.com/badges/7019dcf43672ba8c0e77e7fdd1063398/count.svg
469
502
  [🖐codeQL]: https://github.com/oauth-xx/snaky_hash/security/code-scanning
470
503
  [🖐codeQL-img]: https://github.com/oauth-xx/snaky_hash/actions/workflows/codeql-analysis.yml/badge.svg
471
504
  [🚎1-an-wf]: https://github.com/oauth-xx/snaky_hash/actions/workflows/ancient.yml
@@ -553,7 +586,7 @@ or one of the others at the head of this README.
553
586
  [📌gitmoji]:https://gitmoji.dev
554
587
  [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20😜%20😍-34495e.svg?style=flat-square
555
588
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
556
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.119-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
589
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.132-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
557
590
  [🔐security]: SECURITY.md
558
591
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
559
592
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -581,4 +614,4 @@ CodeCov currently fails to parse the coverage upload.
581
614
 
582
615
  [![Coverage Graph][🔑codecov-g♻️]][🔑codecov]
583
616
 
584
- </details>
617
+ </details>
data/REEK ADDED
@@ -0,0 +1,27 @@
1
+ spec/snaky_hash/snake_spec.rb -- 1 warning:
2
+ [4]:IrresponsibleModule: TheSnakedHash has no descriptive comment [https://github.com/troessner/reek/blob/v6.5.0/docs/Irresponsible-Module.md]
3
+ lib/snaky_hash/extensions.rb -- 1 warning:
4
+ [11]:InstanceVariableAssumption: SnakyHash::Extensions assumes too much for instance variable '@extensions' [https://github.com/troessner/reek/blob/v6.5.0/docs/Instance-Variable-Assumption.md]
5
+ lib/snaky_hash/serializer.rb -- 7 warnings:
6
+ [132]:NilCheck: SnakyHash::Serializer#blank? performs a nil-check [https://github.com/troessner/reek/blob/v6.5.0/docs/Nil-Check.md]
7
+ [180]:TooManyStatements: SnakyHash::Serializer#load_hash has approx 6 statements [https://github.com/troessner/reek/blob/v6.5.0/docs/Too-Many-Statements.md]
8
+ [99]:TooManyStatements: SnakyHash::Serializer::BackportedInstanceMethods#transform_values has approx 7 statements [https://github.com/troessner/reek/blob/v6.5.0/docs/Too-Many-Statements.md]
9
+ [58]:TooManyStatements: SnakyHash::Serializer::Modulizer#to_extended_mod has approx 13 statements [https://github.com/troessner/reek/blob/v6.5.0/docs/Too-Many-Statements.md]
10
+ [170]:UncommunicativeVariableName: SnakyHash::Serializer#dump_value has the variable name 'v' [https://github.com/troessner/reek/blob/v6.5.0/docs/Uncommunicative-Variable-Name.md]
11
+ [214]:UncommunicativeVariableName: SnakyHash::Serializer#load_value has the variable name 'v' [https://github.com/troessner/reek/blob/v6.5.0/docs/Uncommunicative-Variable-Name.md]
12
+ [131]:UtilityFunction: SnakyHash::Serializer#blank? doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/v6.5.0/docs/Utility-Function.md]
13
+ lib/snaky_hash/snake.rb -- 11 warnings:
14
+ [30]:BooleanParameter: SnakyHash::Snake#initialize has boolean parameter 'serializer' [https://github.com/troessner/reek/blob/v6.5.0/docs/Boolean-Parameter.md]
15
+ [69, 75]:DuplicateMethodCall: SnakyHash::Snake::SnakyModulizer#to_mod calls 'define_method(:convert_key)' 2 times [https://github.com/troessner/reek/blob/v6.5.0/docs/Duplicate-Method-Call.md]
16
+ [69, 75]:DuplicateMethodCall: SnakyHash::Snake::SnakyModulizer#to_mod calls 'key.respond_to?(:to_sym)' 2 times [https://github.com/troessner/reek/blob/v6.5.0/docs/Duplicate-Method-Call.md]
17
+ [69, 75]:DuplicateMethodCall: SnakyHash::Snake::SnakyModulizer#to_mod calls 'key.to_s' 2 times [https://github.com/troessner/reek/blob/v6.5.0/docs/Duplicate-Method-Call.md]
18
+ [87, 91]:DuplicateMethodCall: SnakyHash::Snake::SnakyModulizer#to_mod calls 'self.class' 2 times [https://github.com/troessner/reek/blob/v6.5.0/docs/Duplicate-Method-Call.md]
19
+ [69, 75]:DuplicateMethodCall: SnakyHash::Snake::SnakyModulizer#to_mod calls 'underscore_string(key.to_s)' 2 times [https://github.com/troessner/reek/blob/v6.5.0/docs/Duplicate-Method-Call.md]
20
+ [88, 90]:DuplicateMethodCall: SnakyHash::Snake::SnakyModulizer#to_mod calls 'val.dup' 2 times [https://github.com/troessner/reek/blob/v6.5.0/docs/Duplicate-Method-Call.md]
21
+ [69, 75]:ManualDispatch: SnakyHash::Snake::SnakyModulizer#to_mod manually dispatches method call [https://github.com/troessner/reek/blob/v6.5.0/docs/Manual-Dispatch.md]
22
+ [93]:NestedIterators: SnakyHash::Snake::SnakyModulizer#to_mod contains iterators nested 2 deep [https://github.com/troessner/reek/blob/v6.5.0/docs/Nested-Iterators.md]
23
+ [56]:TooManyStatements: SnakyHash::Snake::SnakyModulizer#to_mod has approx 17 statements [https://github.com/troessner/reek/blob/v6.5.0/docs/Too-Many-Statements.md]
24
+ [93]:UncommunicativeVariableName: SnakyHash::Snake::SnakyModulizer#to_mod has the variable name 'e' [https://github.com/troessner/reek/blob/v6.5.0/docs/Uncommunicative-Variable-Name.md]
25
+ .yard_gfm_support.rb -- 1 warning:
26
+ [9, 9]:FeatureEnvy: KramdownGfmDocument#initialize refers to 'options' more than self (maybe move it to another class?) [https://github.com/troessner/reek/blob/v6.5.0/docs/Feature-Envy.md]
27
+ 21 total warnings
File without changes
@@ -21,9 +21,10 @@ module SnakyHash
21
21
  def extended(base)
22
22
  extended_module = Modulizer.to_extended_mod
23
23
  base.extend(extended_module)
24
+ base.include(ConvenienceInstanceMethods)
24
25
  # :nocov:
25
26
  # This will be run in CI on Ruby 2.3, but we only collect coverage from current Ruby
26
- unless base.instance_methods.include?(:transform_values)
27
+ unless base.method_defined?(:transform_values)
27
28
  base.include(BackportedInstanceMethods)
28
29
  end
29
30
  # :nocov:
@@ -45,8 +46,7 @@ module SnakyHash
45
46
  # @return [Hash] deserialized hash object
46
47
  def load(raw_hash)
47
48
  hash = JSON.parse(presence(raw_hash) || "{}")
48
- hash = load_value(new(hash))
49
- new(hash)
49
+ load_hash(new(hash))
50
50
  end
51
51
 
52
52
  # Internal module for generating extension methods
@@ -57,17 +57,29 @@ module SnakyHash
57
57
  # @return [Module] a module containing extension management methods
58
58
  def to_extended_mod
59
59
  Module.new do
60
+ define_method :load_value_extensions do
61
+ @load_value_extensions ||= Extensions.new
62
+ end
63
+
60
64
  define_method :load_extensions do
61
- @load_extensions ||= Extensions.new
65
+ load_value_extensions
66
+ end
67
+
68
+ define_method :dump_value_extensions do
69
+ @dump_value_extensions ||= Extensions.new
62
70
  end
63
71
 
64
72
  define_method :dump_extensions do
65
- @dump_extensions ||= Extensions.new
73
+ dump_value_extensions
66
74
  end
67
75
 
68
76
  define_method :load_hash_extensions do
69
77
  @load_hash_extensions ||= Extensions.new
70
78
  end
79
+
80
+ define_method :dump_hash_extensions do
81
+ @dump_hash_extensions ||= Extensions.new
82
+ end
71
83
  end
72
84
  end
73
85
  end
@@ -96,6 +108,20 @@ module SnakyHash
96
108
  # :nocov:
97
109
  end
98
110
 
111
+ # Provides convenient instance methods for serialization
112
+ #
113
+ # @example Using convenience methods
114
+ # hash = MyHash.new(key: 'value')
115
+ # json = hash.dump #=> '{"key":"value"}'
116
+ module ConvenienceInstanceMethods
117
+ # Serializes the current hash instance to JSON
118
+ #
119
+ # @return [String] JSON string representation of the hash
120
+ def dump
121
+ self.class.dump(self)
122
+ end
123
+ end
124
+
99
125
  private
100
126
 
101
127
  # Checks if a value is blank (nil or empty string)
@@ -117,15 +143,14 @@ module SnakyHash
117
143
  blank?(value) ? nil : value
118
144
  end
119
145
 
120
- # Processes a hash for dumping, transforming its values
146
+ # Processes a hash for dumping, transforming its keys and/or values
121
147
  #
122
148
  # @param hash [Hash] hash to process
123
149
  # @return [Hash] processed hash with transformed values
124
150
  def dump_hash(hash)
125
- hash = self[hash].transform_values do |value|
151
+ dump_hash_extensions.run(self[hash]).transform_values do |value|
126
152
  dump_value(value)
127
153
  end
128
- hash.reject { |_, v| blank?(v) }
129
154
  end
130
155
 
131
156
  # Processes a single value for dumping
@@ -134,7 +159,7 @@ module SnakyHash
134
159
  # @return [Object, nil] processed value
135
160
  def dump_value(value)
136
161
  if blank?(value)
137
- return
162
+ return value
138
163
  end
139
164
 
140
165
  if value.is_a?(::Hash)
@@ -148,28 +173,46 @@ module SnakyHash
148
173
  dump_extensions.run(value)
149
174
  end
150
175
 
151
- # Processes a hash for loading, transforming its values
176
+ # Processes a hash for loading, transforming its keys and/or values
152
177
  #
153
178
  # @param hash [Hash] hash to process
154
179
  # @return [Hash] processed hash with transformed values
155
180
  def load_hash(hash)
156
- hash.transform_values do |value|
181
+ ran = load_hash_extensions.run(self[hash])
182
+ return load_value(ran) unless ran.is_a?(::Hash)
183
+
184
+ res = self[ran].transform_values do |value|
157
185
  load_value(value)
158
186
  end
187
+
188
+ # TODO: Drop this hack when dropping support for Ruby 2.6
189
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7")
190
+ res
191
+ else
192
+ # :nocov:
193
+ # In Ruby <= 2.6 Hash#transform_values returned a new vanilla Hash,
194
+ # rather than a hash of the class being transformed.
195
+ self[res]
196
+ # :nocov:
197
+ end
159
198
  end
160
199
 
161
200
  # Processes a single value for loading
162
201
  #
163
202
  # @param value [Object] value to process
164
- # @return [Object] processed value
203
+ # @return [Object, nil] processed value
165
204
  def load_value(value)
205
+ if blank?(value)
206
+ return value
207
+ end
208
+
166
209
  if value.is_a?(::Hash)
167
- hash = load_hash_extensions.run(new(value))
168
- return load_hash(new(hash)) if hash.is_a?(::Hash)
169
- return load_value(hash)
210
+ return load_hash(value)
170
211
  end
171
212
 
172
- return value.map { |v| load_value(v) } if value.is_a?(Array)
213
+ if value.is_a?(::Array)
214
+ return value.map { |v| load_value(v) }.compact
215
+ end
173
216
 
174
217
  load_extensions.run(value)
175
218
  end
@@ -57,7 +57,12 @@ module SnakyHash
57
57
  Module.new do
58
58
  case key_type
59
59
  when :string then
60
- # Converts a key to a string if possible, after underscoring
60
+ # Converts a key to a string if it is symbolizable, after underscoring
61
+ #
62
+ # @note checks for to_sym instead of to_s, because nearly everything responds_to?(:to_s)
63
+ # so respond_to?(:to_s) isn't very useful as a test, and would result in symbolizing integers
64
+ # amd it also provides parity between the :symbol behavior, and the :string behavior,
65
+ # regarding which keys get converted for a given version of Ruby.
61
66
  #
62
67
  # @param key [Object] the key to convert
63
68
  # @return [String, Object] the converted key or original if not convertible
File without changes
File without changes
@@ -8,6 +8,7 @@ module SnakyHash
8
8
  # Current version of SnakyHash
9
9
  #
10
10
  # @return [String] the current version in semantic versioning format
11
- VERSION = "2.0.2"
11
+ VERSION = "2.0.4"
12
12
  end
13
+ VERSION = Version::VERSION # Traditional Constant Location
13
14
  end
data/lib/snaky_hash.rb CHANGED
File without changes
@@ -0,0 +1,4 @@
1
+ module SnakyHash
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snaky_hash
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Boling
@@ -35,7 +35,7 @@ cert_chain:
35
35
  DVjBtqT23eugOqQ73umLcYDZkc36vnqGxUBSsXrzY9pzV5gGr2I8YUxMqf6ATrZt
36
36
  L9nRqA==
37
37
  -----END CERTIFICATE-----
38
- date: 2025-05-21 00:00:00.000000000 Z
38
+ date: 1980-01-02 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: hashie
@@ -78,25 +78,33 @@ dependencies:
78
78
  - !ruby/object:Gem::Version
79
79
  version: '3'
80
80
  - !ruby/object:Gem::Dependency
81
- name: backports
81
+ name: kettle-dev
82
82
  requirement: !ruby/object:Gem::Requirement
83
83
  requirements:
84
84
  - - "~>"
85
85
  - !ruby/object:Gem::Version
86
- version: '3.25'
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: 3.25.1
86
+ version: '2.0'
90
87
  type: :development
91
88
  prerelease: false
92
89
  version_requirements: !ruby/object:Gem::Requirement
93
90
  requirements:
94
91
  - - "~>"
95
92
  - !ruby/object:Gem::Version
96
- version: '3.25'
97
- - - ">="
93
+ version: '2.0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: bundler-audit
96
+ requirement: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
98
99
  - !ruby/object:Gem::Version
99
- version: 3.25.1
100
+ version: 0.9.2
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: 0.9.2
100
108
  - !ruby/object:Gem::Dependency
101
109
  name: rake
102
110
  requirement: !ruby/object:Gem::Requirement
@@ -112,21 +120,47 @@ dependencies:
112
120
  - !ruby/object:Gem::Version
113
121
  version: '13.0'
114
122
  - !ruby/object:Gem::Dependency
115
- name: rspec
123
+ name: require_bench
124
+ requirement: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '1.0'
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 1.0.4
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.0'
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: 1.0.4
142
+ - !ruby/object:Gem::Dependency
143
+ name: appraisal2
116
144
  requirement: !ruby/object:Gem::Requirement
117
145
  requirements:
118
146
  - - "~>"
119
147
  - !ruby/object:Gem::Version
120
- version: '3.13'
148
+ version: '3.0'
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: 3.0.6
121
152
  type: :development
122
153
  prerelease: false
123
154
  version_requirements: !ruby/object:Gem::Requirement
124
155
  requirements:
125
156
  - - "~>"
126
157
  - !ruby/object:Gem::Version
127
- version: '3.13'
158
+ version: '3.0'
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: 3.0.6
128
162
  - !ruby/object:Gem::Dependency
129
- name: rspec-block_is_expected
163
+ name: kettle-test
130
164
  requirement: !ruby/object:Gem::Requirement
131
165
  requirements:
132
166
  - - "~>"
@@ -134,7 +168,7 @@ dependencies:
134
168
  version: '1.0'
135
169
  - - ">="
136
170
  - !ruby/object:Gem::Version
137
- version: 1.0.6
171
+ version: 1.0.10
138
172
  type: :development
139
173
  prerelease: false
140
174
  version_requirements: !ruby/object:Gem::Requirement
@@ -144,34 +178,51 @@ dependencies:
144
178
  version: '1.0'
145
179
  - - ">="
146
180
  - !ruby/object:Gem::Version
147
- version: 1.0.6
181
+ version: 1.0.10
148
182
  - !ruby/object:Gem::Dependency
149
- name: rspec-pending_for
183
+ name: ruby-progressbar
150
184
  requirement: !ruby/object:Gem::Requirement
151
185
  requirements:
152
186
  - - "~>"
153
187
  - !ruby/object:Gem::Version
154
- version: '0.1'
188
+ version: '1.13'
189
+ type: :development
190
+ prerelease: false
191
+ version_requirements: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - "~>"
194
+ - !ruby/object:Gem::Version
195
+ version: '1.13'
196
+ - !ruby/object:Gem::Dependency
197
+ name: stone_checksums
198
+ requirement: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - "~>"
201
+ - !ruby/object:Gem::Version
202
+ version: '1.0'
155
203
  - - ">="
156
204
  - !ruby/object:Gem::Version
157
- version: 0.1.17
205
+ version: 1.0.3
158
206
  type: :development
159
207
  prerelease: false
160
208
  version_requirements: !ruby/object:Gem::Requirement
161
209
  requirements:
162
210
  - - "~>"
163
211
  - !ruby/object:Gem::Version
164
- version: '0.1'
212
+ version: '1.0'
165
213
  - - ">="
166
214
  - !ruby/object:Gem::Version
167
- version: 0.1.17
215
+ version: 1.0.3
168
216
  - !ruby/object:Gem::Dependency
169
- name: stone_checksums
217
+ name: gitmoji-regex
170
218
  requirement: !ruby/object:Gem::Requirement
171
219
  requirements:
172
220
  - - "~>"
173
221
  - !ruby/object:Gem::Version
174
222
  version: '1.0'
223
+ - - ">="
224
+ - !ruby/object:Gem::Version
225
+ version: 1.0.3
175
226
  type: :development
176
227
  prerelease: false
177
228
  version_requirements: !ruby/object:Gem::Requirement
@@ -179,25 +230,54 @@ dependencies:
179
230
  - - "~>"
180
231
  - !ruby/object:Gem::Version
181
232
  version: '1.0'
182
- description: 'A Hashie::Mash joint to make #snakelife better'
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: 1.0.3
236
+ - !ruby/object:Gem::Dependency
237
+ name: backports
238
+ requirement: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - "~>"
241
+ - !ruby/object:Gem::Version
242
+ version: '3.25'
243
+ - - ">="
244
+ - !ruby/object:Gem::Version
245
+ version: 3.25.1
246
+ type: :development
247
+ prerelease: false
248
+ version_requirements: !ruby/object:Gem::Requirement
249
+ requirements:
250
+ - - "~>"
251
+ - !ruby/object:Gem::Version
252
+ version: '3.25'
253
+ - - ">="
254
+ - !ruby/object:Gem::Version
255
+ version: 3.25.1
256
+ description: "\U0001F40D A Hashie::Mash joint to make #snakelife better"
183
257
  email:
184
- - peter.boling@gmail.com
258
+ - floss@galtzo.com
185
259
  - oauth-ruby@googlegroups.com
186
260
  executables: []
187
261
  extensions: []
188
262
  extra_rdoc_files:
189
263
  - CHANGELOG.md
264
+ - CITATION.cff
190
265
  - CODE_OF_CONDUCT.md
191
266
  - CONTRIBUTING.md
267
+ - IRP.md
192
268
  - LICENSE.txt
193
269
  - README.md
270
+ - REEK
194
271
  - SECURITY.md
195
272
  files:
196
273
  - CHANGELOG.md
274
+ - CITATION.cff
197
275
  - CODE_OF_CONDUCT.md
198
276
  - CONTRIBUTING.md
277
+ - IRP.md
199
278
  - LICENSE.txt
200
279
  - README.md
280
+ - REEK
201
281
  - SECURITY.md
202
282
  - lib/snaky_hash.rb
203
283
  - lib/snaky_hash/extensions.rb
@@ -206,30 +286,29 @@ files:
206
286
  - lib/snaky_hash/string_keyed.rb
207
287
  - lib/snaky_hash/symbol_keyed.rb
208
288
  - lib/snaky_hash/version.rb
209
- homepage: https://github.com/oauth-xx/snaky_hash
289
+ - sig/snaky_hash.rbs
290
+ homepage: https://github.com/ruby-oauth/snaky_hash
210
291
  licenses:
211
292
  - MIT
212
293
  metadata:
213
294
  homepage_uri: https://snaky-hash.galtzo.com/
214
- source_code_uri: https://github.com/oauth-xx/snaky_hash/releases/tag//v2.0.2
215
- changelog_uri: https://gitlab.com/oauth-xx/snaky_hash/-/blob/v2.0.2/CHANGELOG.md
216
- bug_tracker_uri: https://gitlab.com/oauth-xx/snaky_hash/-/issues
217
- documentation_uri: https://www.rubydoc.info/gems/snaky_hash/2.0.2
218
- wiki_uri: https://gitlab.com/oauth-xx/snaky_hash/-/wiki
295
+ source_code_uri: https://github.com/ruby-oauth/snaky_hash/tree/v2.0.4
296
+ changelog_uri: https://github.com/ruby-oauth/snaky_hash/blob/v2.0.4/CHANGELOG.md
297
+ bug_tracker_uri: https://github.com/ruby-oauth/snaky_hash/issues
298
+ documentation_uri: https://www.rubydoc.info/gems/snaky_hash/2.0.4
219
299
  mailing_list_uri: https://groups.google.com/g/oauth-ruby
220
- funding_uri: https://liberapay.com/pboling
300
+ funding_uri: https://github.com/sponsors/pboling
301
+ wiki_uri: https://gitlab.com/ruby-oauth/snaky_hash/-/wiki
221
302
  news_uri: https://www.railsbling.com/tags/snaky_hash
303
+ discord_uri: https://discord.gg/3qme4XHNKN
222
304
  rubygems_mfa_required: 'true'
223
305
  rdoc_options:
224
306
  - "--title"
225
- - snaky_hash - A very snaky hash
307
+ - "snaky_hash - \U0001F40D A very snaky hash"
226
308
  - "--main"
227
- - CHANGELOG.md
228
- - CODE_OF_CONDUCT.md
229
- - CONTRIBUTING.md
230
- - LICENSE.txt
231
309
  - README.md
232
- - SECURITY.md
310
+ - "--exclude"
311
+ - "^sig/"
233
312
  - "--line-numbers"
234
313
  - "--inline-source"
235
314
  - "--quiet"
@@ -246,7 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
325
  - !ruby/object:Gem::Version
247
326
  version: '0'
248
327
  requirements: []
249
- rubygems_version: 3.6.9
328
+ rubygems_version: 4.0.11
250
329
  specification_version: 4
251
- summary: A very snaky hash
330
+ summary: "\U0001F40D A very snaky hash"
252
331
  test_files: []
metadata.gz.sig CHANGED
Binary file