everythingrb 0.9.0 → 1.0.0
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 +4 -4
- data/CHANGELOG.md +23 -7
- data/README.md +78 -118
- data/Rakefile +24 -0
- data/benchmarks/array_benchmark.rb +108 -0
- data/benchmarks/benchmark_helper.rb +42 -0
- data/benchmarks/enumerable_benchmark.rb +80 -0
- data/benchmarks/hash_benchmark.rb +160 -0
- data/benchmarks/kernel_benchmark.rb +51 -0
- data/benchmarks/module_benchmark.rb +106 -0
- data/benchmarks/ostruct_benchmark.rb +84 -0
- data/benchmarks/quotable_benchmark.rb +111 -0
- data/benchmarks/run_all.rb +74 -0
- data/benchmarks/string_benchmark.rb +85 -0
- data/lib/everythingrb/array.rb +0 -45
- data/lib/everythingrb/data.rb +0 -19
- data/lib/everythingrb/date.rb +2 -0
- data/lib/everythingrb/hash.rb +7 -108
- data/lib/everythingrb/module.rb +79 -14
- data/lib/everythingrb/ostruct.rb +0 -20
- data/lib/everythingrb/string.rb +26 -52
- data/lib/everythingrb/struct.rb +0 -21
- data/lib/everythingrb/version.rb +1 -1
- metadata +12 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "benchmark_helper"
|
|
4
|
+
|
|
5
|
+
BenchmarkHelper.header("String Extension Benchmarks")
|
|
6
|
+
|
|
7
|
+
# Test data
|
|
8
|
+
simple_json = '{"name": "Alice", "age": 30}'
|
|
9
|
+
nested_json = '{"user": {"profile": {"name": "Alice"}, "settings": {"theme": "dark"}}}'
|
|
10
|
+
large_json = "{" + (1..100).map { |i| %("key#{i}": #{i}) }.join(", ") + "}"
|
|
11
|
+
invalid_json = "not valid json"
|
|
12
|
+
|
|
13
|
+
simple_string = "hello_world"
|
|
14
|
+
complex_string = "welcome to the jungle!"
|
|
15
|
+
mixed_string = "please-WAIT while_loading..."
|
|
16
|
+
special_chars = "test@#$%^&*()string_with-special.chars!"
|
|
17
|
+
|
|
18
|
+
# ============================================================================
|
|
19
|
+
# parse_json benchmarks
|
|
20
|
+
# ============================================================================
|
|
21
|
+
|
|
22
|
+
BenchmarkHelper.run("parse_json - simple JSON") do |x|
|
|
23
|
+
x.report("parse_json") { simple_json.parse_json }
|
|
24
|
+
x.report("parse_json(symbolize: false)") { simple_json.parse_json(symbolize_names: false) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
BenchmarkHelper.run("parse_json - nested JSON") do |x|
|
|
28
|
+
x.report("parse_json") { nested_json.parse_json }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
BenchmarkHelper.run("parse_json - large JSON (100 keys)") do |x|
|
|
32
|
+
x.report("parse_json") { large_json.parse_json }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
BenchmarkHelper.run("parse_json - invalid JSON") do |x|
|
|
36
|
+
x.report("parse_json (returns nil)") { invalid_json.parse_json }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# ============================================================================
|
|
40
|
+
# to_istruct / to_ostruct / to_struct benchmarks
|
|
41
|
+
# ============================================================================
|
|
42
|
+
|
|
43
|
+
BenchmarkHelper.run("JSON to struct conversions - simple") do |x|
|
|
44
|
+
x.report("to_istruct") { simple_json.to_istruct }
|
|
45
|
+
x.report("to_ostruct") { simple_json.to_ostruct }
|
|
46
|
+
x.report("to_struct") { simple_json.to_struct }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
BenchmarkHelper.run("JSON to struct conversions - nested") do |x|
|
|
50
|
+
x.report("to_istruct") { nested_json.to_istruct }
|
|
51
|
+
x.report("to_ostruct") { nested_json.to_ostruct }
|
|
52
|
+
x.report("to_struct") { nested_json.to_struct }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# ============================================================================
|
|
56
|
+
# to_camelcase benchmarks
|
|
57
|
+
# ============================================================================
|
|
58
|
+
|
|
59
|
+
BenchmarkHelper.run("to_camelcase - simple underscore string") do |x|
|
|
60
|
+
x.report("to_camelcase(:upper)") { simple_string.to_camelcase }
|
|
61
|
+
x.report("to_camelcase(:lower)") { simple_string.to_camelcase(:lower) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
BenchmarkHelper.run("to_camelcase - complex string with spaces") do |x|
|
|
65
|
+
x.report("to_camelcase(:upper)") { complex_string.to_camelcase }
|
|
66
|
+
x.report("to_camelcase(:lower)") { complex_string.to_camelcase(:lower) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
BenchmarkHelper.run("to_camelcase - mixed formatting") do |x|
|
|
70
|
+
x.report("to_camelcase(:upper)") { mixed_string.to_camelcase }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
BenchmarkHelper.run("to_camelcase - special characters") do |x|
|
|
74
|
+
x.report("to_camelcase(:upper)") { special_chars.to_camelcase }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# ============================================================================
|
|
78
|
+
# in_quotes / with_quotes benchmarks
|
|
79
|
+
# ============================================================================
|
|
80
|
+
|
|
81
|
+
BenchmarkHelper.run("in_quotes / with_quotes") do |x|
|
|
82
|
+
x.report("short string in_quotes") { "hello".in_quotes }
|
|
83
|
+
x.report("medium string in_quotes") { complex_string.in_quotes }
|
|
84
|
+
x.report("with_quotes (alias)") { complex_string.with_quotes }
|
|
85
|
+
end
|
data/lib/everythingrb/array.rb
CHANGED
|
@@ -188,49 +188,4 @@ class Array
|
|
|
188
188
|
to_sentence(options)
|
|
189
189
|
end
|
|
190
190
|
end
|
|
191
|
-
|
|
192
|
-
#
|
|
193
|
-
# Recursively converts all elements that respond to #to_h
|
|
194
|
-
#
|
|
195
|
-
# Maps over the array and calls #to_deep_h on any Hash/String elements,
|
|
196
|
-
# #to_h on any objects that respond to it, and handles nested arrays.
|
|
197
|
-
#
|
|
198
|
-
# @return [Array] A new array with all convertible elements deeply converted
|
|
199
|
-
#
|
|
200
|
-
# @example Converting arrays with mixed object types
|
|
201
|
-
# users = [
|
|
202
|
-
# {name: "Alice", roles: ["admin"]},
|
|
203
|
-
# OpenStruct.new(name: "Bob", active: true),
|
|
204
|
-
# Data.define(:name).new(name: "Carol")
|
|
205
|
-
# ]
|
|
206
|
-
# users.to_deep_h
|
|
207
|
-
# # => [
|
|
208
|
-
# # {name: "Alice", roles: ["admin"]},
|
|
209
|
-
# # {name: "Bob", active: true},
|
|
210
|
-
# # {name: "Carol"}
|
|
211
|
-
# # ]
|
|
212
|
-
#
|
|
213
|
-
# @example With nested arrays and JSON strings
|
|
214
|
-
# data = [
|
|
215
|
-
# {profile: '{"level":"expert"}'},
|
|
216
|
-
# [OpenStruct.new(id: 1), OpenStruct.new(id: 2)]
|
|
217
|
-
# ]
|
|
218
|
-
# data.to_deep_h
|
|
219
|
-
# # => [{profile: {level: "expert"}}, [{id: 1}, {id: 2}]]
|
|
220
|
-
#
|
|
221
|
-
def to_deep_h
|
|
222
|
-
map do |value|
|
|
223
|
-
case value
|
|
224
|
-
when Hash
|
|
225
|
-
value.to_deep_h
|
|
226
|
-
when Array
|
|
227
|
-
value.to_deep_h
|
|
228
|
-
when String
|
|
229
|
-
# If the string is not valid JSON, #to_deep_h will return `nil`
|
|
230
|
-
value.to_deep_h || value
|
|
231
|
-
else
|
|
232
|
-
value.respond_to?(:to_h) ? value.to_h : value
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
191
|
end
|
data/lib/everythingrb/data.rb
CHANGED
|
@@ -4,27 +4,8 @@
|
|
|
4
4
|
# Extensions to Ruby's core Data class
|
|
5
5
|
#
|
|
6
6
|
# Provides:
|
|
7
|
-
# - #to_deep_h: Recursively convert to hash with all nested objects
|
|
8
7
|
# - #in_quotes, #with_quotes: Wrap object in quotes
|
|
9
8
|
#
|
|
10
9
|
class Data
|
|
11
10
|
include Everythingrb::InspectQuotable
|
|
12
|
-
|
|
13
|
-
#
|
|
14
|
-
# Recursively converts the Data object and all nested objects to hashes
|
|
15
|
-
#
|
|
16
|
-
# This method traverses the entire Data structure, converting not just
|
|
17
|
-
# the top-level Data object but also nested Data objects, Structs, OpenStructs,
|
|
18
|
-
# and any other objects that implement `to_h`.
|
|
19
|
-
#
|
|
20
|
-
# @return [Hash] A deeply converted hash of the Data object
|
|
21
|
-
#
|
|
22
|
-
# @example
|
|
23
|
-
# Person = Data.define(:name, :profile)
|
|
24
|
-
# person = Person.new(name: "Alice", profile: {roles: ["admin"]})
|
|
25
|
-
# person.to_deep_h # => {name: "Alice", profile: {roles: ["admin"]}}
|
|
26
|
-
#
|
|
27
|
-
def to_deep_h
|
|
28
|
-
to_h.to_deep_h
|
|
29
|
-
end
|
|
30
11
|
end
|
data/lib/everythingrb/date.rb
CHANGED
data/lib/everythingrb/hash.rb
CHANGED
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
# - #join_map: Combine filter_map and join operations
|
|
9
9
|
# - #transform_values.with_key: Transform values with access to keys
|
|
10
10
|
# - #transform, #transform!: Transform keys and values
|
|
11
|
-
# - #
|
|
11
|
+
# - #find_value, #select_values: Find values based on conditions
|
|
12
12
|
# - #rename_key, #rename_keys: Rename hash keys while preserving order
|
|
13
|
-
# - ::new_nested_hash: Create automatically nesting hashes
|
|
14
13
|
# - #merge_if, #merge_if!: Conditionally merge based on key-value pairs
|
|
15
14
|
# - #merge_if_values, #merge_if_values!: Conditionally merge based on values
|
|
16
15
|
# - #compact_merge, #compact_merge!: Merge only non-nil values
|
|
@@ -37,59 +36,6 @@ class Hash
|
|
|
37
36
|
#
|
|
38
37
|
EMPTY_STRUCT = Struct.new(:_).new(nil)
|
|
39
38
|
|
|
40
|
-
#
|
|
41
|
-
# Creates a new Hash that automatically initializes missing keys with nested hashes
|
|
42
|
-
#
|
|
43
|
-
# This method creates a hash where any missing key access will automatically
|
|
44
|
-
# create another nested hash with the same behavior. You can control the nesting
|
|
45
|
-
# depth with the depth parameter.
|
|
46
|
-
#
|
|
47
|
-
# @param depth [Integer, nil] The maximum nesting depth for automatic hash creation
|
|
48
|
-
# When nil (default), creates unlimited nesting depth
|
|
49
|
-
# When 0, behaves like a regular hash (returns nil for missing keys)
|
|
50
|
-
# When > 0, automatically creates hashes only up to the specified level
|
|
51
|
-
#
|
|
52
|
-
# @return [Hash] A hash that creates nested hashes for missing keys
|
|
53
|
-
#
|
|
54
|
-
# @note This implementation is not thread-safe for concurrent modifications of deeply
|
|
55
|
-
# nested structures. If you need thread safety, consider using a mutex when modifying
|
|
56
|
-
# the deeper levels of the hash.
|
|
57
|
-
#
|
|
58
|
-
# @example Unlimited nesting (default behavior)
|
|
59
|
-
# users = Hash.new_nested_hash
|
|
60
|
-
# users[:john][:role] = "admin" # No need to initialize users[:john] first
|
|
61
|
-
# users # => {john: {role: "admin"}}
|
|
62
|
-
#
|
|
63
|
-
# @example Deep nesting without initialization
|
|
64
|
-
# stats = Hash.new_nested_hash
|
|
65
|
-
# stats[:server][:region][:us_east][:errors] = ["Error"]
|
|
66
|
-
# stats # => {server: {region: {us_east: {errors: ["Error"]}}}}
|
|
67
|
-
#
|
|
68
|
-
# @example Limited nesting depth
|
|
69
|
-
# hash = Hash.new_nested_hash(depth: 1)
|
|
70
|
-
# hash[:user][:name] = "Alice" # Works fine - only one level of auto-creation
|
|
71
|
-
#
|
|
72
|
-
# # This pattern works correctly with limited nesting:
|
|
73
|
-
# (hash[:user][:roles] ||= []) << "admin"
|
|
74
|
-
# hash # => {user: {name: "Alice", roles: ["admin"]}}
|
|
75
|
-
#
|
|
76
|
-
# @note While unlimited nesting is convenient, it can interfere with common Ruby
|
|
77
|
-
# patterns like ||= when initializing values at deep depths. Use the depth
|
|
78
|
-
# parameter to control this behavior.
|
|
79
|
-
#
|
|
80
|
-
def self.new_nested_hash(depth: nil)
|
|
81
|
-
new do |hash, key|
|
|
82
|
-
next if depth == 0
|
|
83
|
-
|
|
84
|
-
hash[key] =
|
|
85
|
-
if depth.nil?
|
|
86
|
-
new_nested_hash
|
|
87
|
-
else
|
|
88
|
-
new_nested_hash(depth: depth - 1)
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
39
|
#
|
|
94
40
|
# Combines filter_map and join operations
|
|
95
41
|
#
|
|
@@ -127,53 +73,6 @@ class Hash
|
|
|
127
73
|
end
|
|
128
74
|
end
|
|
129
75
|
|
|
130
|
-
#
|
|
131
|
-
# Recursively converts all values that respond to #to_h
|
|
132
|
-
#
|
|
133
|
-
# Similar to #to_h but recursively traverses the Hash structure
|
|
134
|
-
# and calls #to_h on any object that responds to it. Useful for
|
|
135
|
-
# normalizing nested data structures and parsing nested JSON.
|
|
136
|
-
#
|
|
137
|
-
# @return [Hash] A deeply converted hash with all nested objects
|
|
138
|
-
#
|
|
139
|
-
# @example Converting nested Data objects
|
|
140
|
-
# user = { name: "Alice", metadata: Data.define(:source).new(source: "API") }
|
|
141
|
-
# user.to_deep_h # => {name: "Alice", metadata: {source: "API"}}
|
|
142
|
-
#
|
|
143
|
-
# @example Parsing nested JSON strings
|
|
144
|
-
# nested = { profile: '{"role":"admin"}' }
|
|
145
|
-
# nested.to_deep_h # => {profile: {role: "admin"}}
|
|
146
|
-
#
|
|
147
|
-
# @example Mixed nested structures
|
|
148
|
-
# data = {
|
|
149
|
-
# config: OpenStruct.new(api_key: "secret"),
|
|
150
|
-
# users: [
|
|
151
|
-
# Data.define(:name).new(name: "Bob"),
|
|
152
|
-
# {role: "admin"}
|
|
153
|
-
# ]
|
|
154
|
-
# }
|
|
155
|
-
# data.to_deep_h
|
|
156
|
-
# # => {
|
|
157
|
-
# # config: {api_key: "secret"},
|
|
158
|
-
# # users: [{name: "Bob"}, {role: "admin"}]
|
|
159
|
-
# # }
|
|
160
|
-
#
|
|
161
|
-
def to_deep_h
|
|
162
|
-
transform_values do |value|
|
|
163
|
-
case value
|
|
164
|
-
when Hash
|
|
165
|
-
value.to_deep_h
|
|
166
|
-
when Array
|
|
167
|
-
value.to_deep_h
|
|
168
|
-
when String
|
|
169
|
-
# If the string is not valid JSON, #to_deep_h will return `nil`
|
|
170
|
-
value.to_deep_h || value
|
|
171
|
-
else
|
|
172
|
-
value.respond_to?(:to_h) ? value.to_h : value
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
76
|
#
|
|
178
77
|
# Converts hash to an immutable Data structure
|
|
179
78
|
#
|
|
@@ -512,10 +411,10 @@ class Hash
|
|
|
512
411
|
# bob: {name: "Bob", role: "user"},
|
|
513
412
|
# charlie: {name: "Charlie", role: "admin"}
|
|
514
413
|
# }
|
|
515
|
-
# users.
|
|
414
|
+
# users.find_value { |k, v| v[:role] == "admin" } # => {name: "Alice", role: "admin"}
|
|
516
415
|
#
|
|
517
|
-
def
|
|
518
|
-
return to_enum(:
|
|
416
|
+
def find_value(&block)
|
|
417
|
+
return to_enum(:find_value) if block.nil?
|
|
519
418
|
|
|
520
419
|
find(&block)&.last
|
|
521
420
|
end
|
|
@@ -537,11 +436,11 @@ class Hash
|
|
|
537
436
|
# bob: {name: "Bob", role: "user"},
|
|
538
437
|
# charlie: {name: "Charlie", role: "admin"}
|
|
539
438
|
# }
|
|
540
|
-
# users.
|
|
439
|
+
# users.select_values { |k, v| v[:role] == "admin" }
|
|
541
440
|
# # => [{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}]
|
|
542
441
|
#
|
|
543
|
-
def
|
|
544
|
-
return to_enum(:
|
|
442
|
+
def select_values(&block)
|
|
443
|
+
return to_enum(:select_values) if block.nil?
|
|
545
444
|
|
|
546
445
|
select(&block).values
|
|
547
446
|
end
|
data/lib/everythingrb/module.rb
CHANGED
|
@@ -20,17 +20,26 @@
|
|
|
20
20
|
#
|
|
21
21
|
class Module
|
|
22
22
|
#
|
|
23
|
-
# Creates predicate (boolean) methods that return true/false
|
|
24
|
-
# Similar to attr_reader, attr_writer, etc. Designed to work with
|
|
25
|
-
#
|
|
23
|
+
# Creates predicate (boolean) methods that return true/false based on an attribute's value.
|
|
24
|
+
# Similar to attr_reader, attr_writer, etc. Designed to work with regular classes, Struct,
|
|
25
|
+
# and Data objects.
|
|
26
26
|
#
|
|
27
|
-
#
|
|
27
|
+
# Values are evaluated as follows:
|
|
28
|
+
# - nil and false are always false
|
|
29
|
+
# - With ActiveSupport: uses +present?+ (empty strings, arrays, hashes are false)
|
|
30
|
+
# - Without ActiveSupport: checks +empty?+ if available, otherwise uses truthiness
|
|
28
31
|
#
|
|
29
32
|
# @param attributes [Array<Symbol, String>] Attribute names
|
|
33
|
+
# @param opts [Hash] Options hash
|
|
34
|
+
# @option opts [Symbol, String, nil] :from Source ivar or method to read from. Use @ prefix
|
|
35
|
+
# for instance variables (e.g., :@started_at), omit for methods (e.g., :status). When
|
|
36
|
+
# specified, only one attribute may be provided (ambiguous mapping otherwise).
|
|
37
|
+
# @option opts [Boolean] :private If true, defines the predicate as a private method
|
|
30
38
|
#
|
|
31
39
|
# @return [nil]
|
|
32
40
|
#
|
|
33
41
|
# @raise [ArgumentError] If a predicate method of the same name already exists
|
|
42
|
+
# @raise [ArgumentError] If from: is specified with multiple attributes
|
|
34
43
|
#
|
|
35
44
|
# @example With a regular class
|
|
36
45
|
# class User
|
|
@@ -50,24 +59,80 @@ class Module
|
|
|
50
59
|
# person = Person.new(active: true)
|
|
51
60
|
# person.active? # => true
|
|
52
61
|
#
|
|
53
|
-
|
|
62
|
+
# @example Mapping to a different ivar with from:
|
|
63
|
+
# class Task
|
|
64
|
+
# attr_accessor :started_at, :stopped_at
|
|
65
|
+
# attr_predicate :started, from: :@started_at
|
|
66
|
+
# attr_predicate :finished, from: :@stopped_at
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# task = Task.new
|
|
70
|
+
# task.started? # => false
|
|
71
|
+
# task.started_at = Time.now
|
|
72
|
+
# task.started? # => true
|
|
73
|
+
#
|
|
74
|
+
# @example Mapping to a method with from:
|
|
75
|
+
# class Job
|
|
76
|
+
# attr_accessor :error_messages
|
|
77
|
+
# attr_predicate :errored, from: :error_messages
|
|
78
|
+
# end
|
|
79
|
+
#
|
|
80
|
+
# job = Job.new
|
|
81
|
+
# job.errored? # => false
|
|
82
|
+
# job.error_messages = ["Something went wrong"]
|
|
83
|
+
# job.errored? # => true
|
|
84
|
+
#
|
|
85
|
+
# @example Private predicate method
|
|
86
|
+
# class Account
|
|
87
|
+
# attr_accessor :verified
|
|
88
|
+
# attr_predicate :verified, private: true
|
|
89
|
+
# end
|
|
90
|
+
#
|
|
91
|
+
# account = Account.new
|
|
92
|
+
# account.verified? # => NoMethodError (private method)
|
|
93
|
+
#
|
|
94
|
+
def attr_predicate(*attributes, **opts)
|
|
95
|
+
from = opts[:from]
|
|
96
|
+
private_method = !!opts[:private]
|
|
97
|
+
|
|
98
|
+
if from && attributes.size > 1
|
|
99
|
+
raise ArgumentError, "Cannot use from: option with multiple attributes - each predicate needs its own source mapping"
|
|
100
|
+
end
|
|
101
|
+
|
|
54
102
|
attributes.each do |attribute|
|
|
55
103
|
if method_defined?(:"#{attribute}?")
|
|
56
104
|
raise ArgumentError, "Cannot create predicate method on #{self.class} - #{attribute}? is already defined. Please choose a different name or remove the existing method."
|
|
57
105
|
end
|
|
58
106
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
107
|
+
signature = "def #{attribute}?"
|
|
108
|
+
signature.prepend("private ") if private_method
|
|
109
|
+
|
|
110
|
+
# Performance note:
|
|
111
|
+
# This was originally checked if an instance variable or method was defined, both of which are sllllooooowwwww
|
|
112
|
+
# Now as of 1.0.0, this assumes an instance variable (with exceptions) by default
|
|
113
|
+
getter =
|
|
114
|
+
if from
|
|
115
|
+
from.to_s.start_with?("@") ? from : "self.#{from}"
|
|
116
|
+
elsif self < Struct || self < OpenStruct || self < Data
|
|
117
|
+
"self.#{attribute}"
|
|
118
|
+
else
|
|
119
|
+
"@#{attribute}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
checker =
|
|
123
|
+
if defined?(ActiveSupport)
|
|
124
|
+
"!!value.presence"
|
|
125
|
+
else
|
|
126
|
+
# Handle empty arrays/hashes/strings
|
|
127
|
+
"value.respond_to?(:empty?) ? !value.empty? : !!value"
|
|
128
|
+
end
|
|
67
129
|
|
|
130
|
+
module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
131
|
+
#{signature}
|
|
132
|
+
value = #{getter}
|
|
68
133
|
return false if value.nil?
|
|
69
134
|
|
|
70
|
-
|
|
135
|
+
#{checker}
|
|
71
136
|
end
|
|
72
137
|
RUBY
|
|
73
138
|
end
|
data/lib/everythingrb/ostruct.rb
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
# - #map, #filter_map: Enumeration methods for OpenStruct entries
|
|
8
8
|
# - #join_map: Combine filter_map and join operations
|
|
9
9
|
# - #blank?, #present?: ActiveSupport integrations when available
|
|
10
|
-
# - #to_deep_h: Recursively convert to hash with all nested objects
|
|
11
10
|
# - #in_quotes, #with_quotes: Wrap struct in quotes
|
|
12
11
|
#
|
|
13
12
|
# @example
|
|
@@ -117,23 +116,4 @@ class OpenStruct
|
|
|
117
116
|
def to_ostruct
|
|
118
117
|
self
|
|
119
118
|
end
|
|
120
|
-
|
|
121
|
-
#
|
|
122
|
-
# Recursively converts the OpenStruct and all nested objects to hashes
|
|
123
|
-
#
|
|
124
|
-
# This method will convert the OpenStruct and all nested OpenStructs,
|
|
125
|
-
# Structs, Data objects, and other convertible objects to plain hashes.
|
|
126
|
-
#
|
|
127
|
-
# @return [Hash] A deeply converted hash of the OpenStruct
|
|
128
|
-
#
|
|
129
|
-
# @example
|
|
130
|
-
# person = OpenStruct.new(
|
|
131
|
-
# name: "Alice",
|
|
132
|
-
# address: OpenStruct.new(city: "New York", country: "USA")
|
|
133
|
-
# )
|
|
134
|
-
# person.to_deep_h # => {name: "Alice", address: {city: "New York", country: "USA"}}
|
|
135
|
-
#
|
|
136
|
-
def to_deep_h
|
|
137
|
-
to_h.to_deep_h
|
|
138
|
-
end
|
|
139
119
|
end
|
data/lib/everythingrb/string.rb
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
# Extensions to Ruby's core String class
|
|
5
5
|
#
|
|
6
6
|
# Provides:
|
|
7
|
-
# - #
|
|
8
|
-
# - #to_deep_h: Recursively parse nested JSON strings
|
|
7
|
+
# - #parse_json: Parse JSON strings with error handling
|
|
9
8
|
# - #to_ostruct, #to_istruct, #to_struct: Convert JSON to data structures
|
|
10
9
|
# - #with_quotes, #in_quotes: Wrap strings in quotes
|
|
11
10
|
# - #to_camelcase: Convert strings to camelCase or PascalCase
|
|
@@ -21,61 +20,36 @@ class String
|
|
|
21
20
|
include Everythingrb::StringQuotable
|
|
22
21
|
|
|
23
22
|
#
|
|
24
|
-
#
|
|
23
|
+
# Parses the string as JSON and returns the result
|
|
25
24
|
#
|
|
26
|
-
#
|
|
25
|
+
# Safely parses JSON with symbolized keys by default. Returns nil
|
|
26
|
+
# instead of raising an exception if the string is not valid JSON.
|
|
27
27
|
#
|
|
28
|
-
# @
|
|
29
|
-
#
|
|
30
|
-
# "invalid json".to_h # => nil
|
|
28
|
+
# @param opts [Hash] Options to pass to JSON.parse
|
|
29
|
+
# @option opts [Boolean] :symbolize_names (true) Whether to symbolize keys
|
|
31
30
|
#
|
|
32
|
-
|
|
33
|
-
JSON.parse(self, symbolize_names: true)
|
|
34
|
-
rescue JSON::ParserError
|
|
35
|
-
nil
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
alias_method :to_a, :to_h
|
|
39
|
-
|
|
31
|
+
# @return [Hash, Array, nil] Parsed JSON or nil if invalid
|
|
40
32
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
33
|
+
# @example Basic usage
|
|
34
|
+
# '{"name": "Alice"}'.parse_json # => {name: "Alice"}
|
|
43
35
|
#
|
|
44
|
-
# @
|
|
45
|
-
#
|
|
36
|
+
# @example With nested data
|
|
37
|
+
# '{"user": {"roles": ["admin"]}}'.parse_json
|
|
38
|
+
# # => {user: {roles: ["admin"]}}
|
|
46
39
|
#
|
|
47
|
-
# @
|
|
48
|
-
#
|
|
40
|
+
# @example Invalid JSON returns nil
|
|
41
|
+
# "not json".parse_json # => nil
|
|
49
42
|
#
|
|
50
|
-
# @example
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
def to_deep_h
|
|
58
|
-
recursive_convert = lambda do |object|
|
|
59
|
-
case object
|
|
60
|
-
when Array
|
|
61
|
-
object.map { |v| recursive_convert.call(v) }
|
|
62
|
-
when String
|
|
63
|
-
result = object.to_deep_h
|
|
64
|
-
|
|
65
|
-
# Nested JSON
|
|
66
|
-
if result.is_a?(Array) || result.is_a?(Hash)
|
|
67
|
-
recursive_convert.call(result)
|
|
68
|
-
else
|
|
69
|
-
object
|
|
70
|
-
end
|
|
71
|
-
when Hash
|
|
72
|
-
object.transform_values { |v| recursive_convert.call(v) }
|
|
73
|
-
else
|
|
74
|
-
object
|
|
75
|
-
end
|
|
76
|
-
end
|
|
43
|
+
# @example Disable symbolized keys
|
|
44
|
+
# '{"name": "Alice"}'.parse_json(symbolize_names: false)
|
|
45
|
+
# # => {"name" => "Alice"}
|
|
46
|
+
#
|
|
47
|
+
def parse_json(**opts)
|
|
48
|
+
opts[:symbolize_names] = true unless opts.key?(:symbolize_names)
|
|
77
49
|
|
|
78
|
-
|
|
50
|
+
JSON.parse(self, opts)
|
|
51
|
+
rescue JSON::ParserError
|
|
52
|
+
nil
|
|
79
53
|
end
|
|
80
54
|
|
|
81
55
|
#
|
|
@@ -89,7 +63,7 @@ class String
|
|
|
89
63
|
# "not json".to_istruct # => nil
|
|
90
64
|
#
|
|
91
65
|
def to_istruct
|
|
92
|
-
|
|
66
|
+
parse_json&.to_istruct
|
|
93
67
|
end
|
|
94
68
|
|
|
95
69
|
#
|
|
@@ -103,7 +77,7 @@ class String
|
|
|
103
77
|
# "not json".to_ostruct # => nil
|
|
104
78
|
#
|
|
105
79
|
def to_ostruct
|
|
106
|
-
|
|
80
|
+
parse_json&.to_ostruct
|
|
107
81
|
end
|
|
108
82
|
|
|
109
83
|
#
|
|
@@ -117,7 +91,7 @@ class String
|
|
|
117
91
|
# "not json".to_struct # => nil
|
|
118
92
|
#
|
|
119
93
|
def to_struct
|
|
120
|
-
|
|
94
|
+
parse_json&.to_struct
|
|
121
95
|
end
|
|
122
96
|
|
|
123
97
|
#
|
data/lib/everythingrb/struct.rb
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
# Extensions to Ruby's core Struct class
|
|
5
5
|
#
|
|
6
6
|
# Provides:
|
|
7
|
-
# - #to_deep_h: Recursively convert to hash with all nested objects
|
|
8
7
|
# - #in_quotes, #with_quotes: Wrap struct in quotes
|
|
9
8
|
#
|
|
10
9
|
# @example
|
|
@@ -12,27 +11,7 @@
|
|
|
12
11
|
#
|
|
13
12
|
# Person = Struct.new(:name, :profile)
|
|
14
13
|
# person = Person.new("Alice", {roles: ["admin"]})
|
|
15
|
-
# person.to_deep_h # => {name: "Alice", profile: {roles: ["admin"]}}
|
|
16
14
|
#
|
|
17
15
|
class Struct
|
|
18
16
|
include Everythingrb::InspectQuotable
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
# Recursively converts the Struct and all nested objects to hashes
|
|
22
|
-
#
|
|
23
|
-
# This method traverses the entire Struct structure, converting not just
|
|
24
|
-
# the top-level Struct but also nested Structs, OpenStructs, Data objects,
|
|
25
|
-
# and any other objects that implement `to_h`.
|
|
26
|
-
#
|
|
27
|
-
# @return [Hash] A deeply converted hash of the Struct
|
|
28
|
-
#
|
|
29
|
-
# @example
|
|
30
|
-
# Address = Struct.new(:city, :country)
|
|
31
|
-
# Person = Struct.new(:name, :address)
|
|
32
|
-
# person = Person.new("Alice", Address.new("New York", "USA"))
|
|
33
|
-
# person.to_deep_h # => {name: "Alice", address: {city: "New York", country: "USA"}}
|
|
34
|
-
#
|
|
35
|
-
def to_deep_h
|
|
36
|
-
to_h.to_deep_h
|
|
37
|
-
end
|
|
38
17
|
end
|
data/lib/everythingrb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: everythingrb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bryan
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ostruct
|
|
@@ -54,6 +54,16 @@ files:
|
|
|
54
54
|
- LICENSE.txt
|
|
55
55
|
- README.md
|
|
56
56
|
- Rakefile
|
|
57
|
+
- benchmarks/array_benchmark.rb
|
|
58
|
+
- benchmarks/benchmark_helper.rb
|
|
59
|
+
- benchmarks/enumerable_benchmark.rb
|
|
60
|
+
- benchmarks/hash_benchmark.rb
|
|
61
|
+
- benchmarks/kernel_benchmark.rb
|
|
62
|
+
- benchmarks/module_benchmark.rb
|
|
63
|
+
- benchmarks/ostruct_benchmark.rb
|
|
64
|
+
- benchmarks/quotable_benchmark.rb
|
|
65
|
+
- benchmarks/run_all.rb
|
|
66
|
+
- benchmarks/string_benchmark.rb
|
|
57
67
|
- flake.lock
|
|
58
68
|
- flake.nix
|
|
59
69
|
- lib/everythingrb.rb
|