optify-from_hash 0.0.1
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 +7 -0
- data/lib/optify-from_hash.rb +4 -0
- data/lib/optify_from_hash/from_hashable.rb +156 -0
- data/rbi/optify_from_hash.rbi +34 -0
- data/sig/optify_from_hash.rbs +27 -0
- metadata +140 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 62d42fbaf4796772d494196254d42eeb79259ff0b7c17b042de42cf4ad401dfd
|
|
4
|
+
data.tar.gz: 8c812657f197ea275174418c9d337a5f6f8c129bdd28e58486c15e0d7a9ac519
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d47d44960600790f9d15ebe0eb9fe1dbcd6c538a5e03bcb5e789d8fe3d68ffde5776930c47dbe848a481bf090501fdd25fffbed3abaef7725e5fb7cb945b23af
|
|
7
|
+
data.tar.gz: 4a6f118ce068585191c3d5193ea9c0ef135f65a653b5193464ddb5a0dce3e3a3e799b85c3f63037b6b9b47cb83b9dcdbcd6b9b9b2f34eb6a04dcc84143275a4c
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
|
+
require 'tapioca'
|
|
6
|
+
|
|
7
|
+
module Optify
|
|
8
|
+
# A base class for classes that can be created from a hash.
|
|
9
|
+
class FromHashable
|
|
10
|
+
extend T::Sig
|
|
11
|
+
extend T::Helpers
|
|
12
|
+
abstract!
|
|
13
|
+
|
|
14
|
+
# Create a new immutable instance of the class from a hash.
|
|
15
|
+
#
|
|
16
|
+
# This is a class method so that it can set members with private setters.
|
|
17
|
+
# @param hash The hash to create the instance from.
|
|
18
|
+
# @return The new instance.
|
|
19
|
+
#: (Hash[untyped, untyped]) -> instance
|
|
20
|
+
def self.from_hash(hash)
|
|
21
|
+
instance = new
|
|
22
|
+
|
|
23
|
+
hash.each do |key, value|
|
|
24
|
+
begin
|
|
25
|
+
method = instance_method(key)
|
|
26
|
+
rescue StandardError
|
|
27
|
+
raise ArgumentError,
|
|
28
|
+
"Error converting hash to `#{name}` because of key \"#{key}\". Perhaps \"#{key}\" is not a valid attribute for `#{name}`."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig = T::Utils.signature_for_method(method)
|
|
32
|
+
raise "A Sorbet signature is required for `#{name}.#{key}`." if sig.nil?
|
|
33
|
+
|
|
34
|
+
sig_return_type = sig.return_type
|
|
35
|
+
value = _convert_value(value, sig_return_type)
|
|
36
|
+
instance.instance_variable_set("@#{key}", value)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
instance.freeze
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#: (untyped, untyped) -> untyped
|
|
43
|
+
def self._convert_value(value, type)
|
|
44
|
+
if type.is_a?(T::Types::Untyped)
|
|
45
|
+
# No preferred type is given, so return the value as is.
|
|
46
|
+
return value
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
return value.to_sym if type.is_a?(T::Types::Simple) && type.raw_type == Symbol
|
|
50
|
+
|
|
51
|
+
case value
|
|
52
|
+
when Array
|
|
53
|
+
# Handle `T.nilable(T::Array[...])`
|
|
54
|
+
type = type.unwrap_nilable if type.respond_to?(:unwrap_nilable)
|
|
55
|
+
inner_type = type.type
|
|
56
|
+
return value.map { |v| _convert_value(v, inner_type) }.freeze
|
|
57
|
+
when Hash
|
|
58
|
+
# Handle `T.nilable(T::Hash[...])` and `T.any(...)`.
|
|
59
|
+
# We used to use `type = type.unwrap_nilable if type.respond_to?(:unwrap_nilable)`, but it's not needed now that we handle
|
|
60
|
+
# `T.any(...)` because using `.types` works for both cases.
|
|
61
|
+
if type.respond_to?(:types)
|
|
62
|
+
# Find a type that works for the hash.
|
|
63
|
+
type.types.each do |t|
|
|
64
|
+
return _convert_hash(value, t).freeze
|
|
65
|
+
rescue StandardError
|
|
66
|
+
# Ignore and try the next type.
|
|
67
|
+
end
|
|
68
|
+
raise TypeError, "Could not convert hash: #{value} to #{type}."
|
|
69
|
+
end
|
|
70
|
+
return _convert_hash(value, type).freeze
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# It would be nice to validate that the value is of the correct type here.
|
|
74
|
+
# For example that a string is a string and an Integer is an Integer.
|
|
75
|
+
value
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
#: (Hash[untyped, untyped], untyped) -> untyped
|
|
79
|
+
def self._convert_hash(hash, type)
|
|
80
|
+
if type.respond_to?(:raw_type)
|
|
81
|
+
# There is an object for the hash.
|
|
82
|
+
# It could be a custom class, a String, or maybe something else.
|
|
83
|
+
type_for_hash = type.raw_type
|
|
84
|
+
return type_for_hash.from_hash(hash) if type_for_hash.respond_to?(:from_hash)
|
|
85
|
+
elsif type.is_a?(T::Types::TypedHash)
|
|
86
|
+
# The hash should be a hash, but the values might be objects to convert.
|
|
87
|
+
type_for_keys = type.keys
|
|
88
|
+
|
|
89
|
+
convert_key = if type_for_keys.is_a?(T::Types::Simple) && type_for_keys.raw_type == Symbol
|
|
90
|
+
lambda(&:to_sym)
|
|
91
|
+
else
|
|
92
|
+
lambda(&:itself)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
type_for_values = type.values
|
|
96
|
+
return hash.map { |k, v| [convert_key.call(k), _convert_value(v, type_for_values)] }.to_h
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
raise TypeError, "Could not convert hash #{hash} to `#{type}`."
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private_class_method :_convert_hash, :_convert_value
|
|
103
|
+
|
|
104
|
+
# Compare this object with another object for equality.
|
|
105
|
+
# @param other The object to compare.
|
|
106
|
+
# @return [Boolean] true if the objects are equal; otherwise, false.
|
|
107
|
+
#: (untyped) -> bool
|
|
108
|
+
def ==(other)
|
|
109
|
+
return true if other.equal?(self)
|
|
110
|
+
return false unless other.is_a?(self.class)
|
|
111
|
+
|
|
112
|
+
instance_variables.all? do |name|
|
|
113
|
+
instance_variable_get(name) == other.instance_variable_get(name)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Convert this object to a Hash recursively.
|
|
118
|
+
# This is mostly the reverse operation of `from_hash`,
|
|
119
|
+
# as keys will be symbols
|
|
120
|
+
# and `from_hash` will convert strings to symbols if that's how the attribute is declared.
|
|
121
|
+
# @return [Hash] The hash representation of this object.
|
|
122
|
+
#: () -> Hash[Symbol, untyped]
|
|
123
|
+
def to_h
|
|
124
|
+
result = Hash.new(instance_variables.size)
|
|
125
|
+
|
|
126
|
+
instance_variables.each do |var_name|
|
|
127
|
+
# Remove the @ prefix to get the method name
|
|
128
|
+
method_name = var_name.to_s[1..] #: as !nil
|
|
129
|
+
value = instance_variable_get(var_name)
|
|
130
|
+
result[method_name.to_sym] = _convert_value_to_hash(value)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
result
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
#: (untyped) -> untyped
|
|
139
|
+
def _convert_value_to_hash(value)
|
|
140
|
+
case value
|
|
141
|
+
when Array
|
|
142
|
+
value.map { |v| _convert_value_to_hash(v) }
|
|
143
|
+
when Hash
|
|
144
|
+
value.transform_values { |v| _convert_value_to_hash(v) }
|
|
145
|
+
when nil
|
|
146
|
+
nil
|
|
147
|
+
else
|
|
148
|
+
if value.respond_to?(:to_h)
|
|
149
|
+
value.to_h
|
|
150
|
+
else
|
|
151
|
+
value
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strong
|
|
3
|
+
|
|
4
|
+
# Tools for working with configurations declared in files.
|
|
5
|
+
module Optify
|
|
6
|
+
# A base class for classes that can be created from a hash.
|
|
7
|
+
class FromHashable
|
|
8
|
+
extend T::Sig
|
|
9
|
+
extend T::Helpers
|
|
10
|
+
abstract!
|
|
11
|
+
|
|
12
|
+
# Create a new instance of the class from a hash.
|
|
13
|
+
#
|
|
14
|
+
# This is a class method that so that it can set members with private setters.
|
|
15
|
+
# @param hash The hash to create the instance from.
|
|
16
|
+
# @return The new instance.
|
|
17
|
+
sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T.attached_class) }
|
|
18
|
+
def self.from_hash(hash); end
|
|
19
|
+
|
|
20
|
+
# Convert this object to a Hash recursively.
|
|
21
|
+
# This is mostly the reverse operation of `from_hash`,
|
|
22
|
+
# as keys will be symbols
|
|
23
|
+
# and `from_hash` will convert strings to symbols if that's how the attribute is declared.
|
|
24
|
+
# @return The hash representation of this object.
|
|
25
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
|
26
|
+
def to_h; end
|
|
27
|
+
|
|
28
|
+
# Compare this object with another object for equality.
|
|
29
|
+
# @param other The object to compare.
|
|
30
|
+
# @return [Boolean] true if the objects are equal; otherwise, false.
|
|
31
|
+
sig { params(other: T.untyped).returns(T::Boolean) }
|
|
32
|
+
def ==(other); end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Tools for working with configurations declared in files.
|
|
2
|
+
module Optify
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
# A base class for classes that can be created from a hash.
|
|
6
|
+
class Optify::FromHashable
|
|
7
|
+
extend T::Helpers
|
|
8
|
+
|
|
9
|
+
# Create a new instance of the class from a hash.
|
|
10
|
+
#
|
|
11
|
+
# This is a class method that so that it can set members with private setters.
|
|
12
|
+
# @param hash The hash to create the instance from.
|
|
13
|
+
# @return The new instance.
|
|
14
|
+
def self.from_hash: (::Hash[untyped, untyped] hash) -> instance
|
|
15
|
+
|
|
16
|
+
# Convert this object to a Hash recursively.
|
|
17
|
+
# This is mostly the reverse operation of `from_hash`,
|
|
18
|
+
# as keys will be symbols
|
|
19
|
+
# and `from_hash` will convert strings to symbols if that's how the attribute is declared.
|
|
20
|
+
# @return The hash representation of this object.
|
|
21
|
+
def to_h: () -> ::Hash[Symbol, untyped]
|
|
22
|
+
|
|
23
|
+
# Compare this object with another object for equality.
|
|
24
|
+
# @param other The object to compare.
|
|
25
|
+
# @return [Boolean] true if the objects are equal; otherwise, false.
|
|
26
|
+
def ==: (untyped other) -> bool
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: optify-from_hash
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Justin D. Harris
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: sorbet-runtime
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.5'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '1'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '0.5'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '1'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: rake
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0'
|
|
39
|
+
type: :development
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rbs
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: 4.0.0.dev.4
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 4.0.0.dev.4
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: sorbet
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0.5'
|
|
67
|
+
- - "<"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '1'
|
|
70
|
+
type: :development
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '0.5'
|
|
77
|
+
- - "<"
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '1'
|
|
80
|
+
- !ruby/object:Gem::Dependency
|
|
81
|
+
name: tapioca
|
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - "~>"
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: 0.17.7
|
|
87
|
+
type: :development
|
|
88
|
+
prerelease: false
|
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - "~>"
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: 0.17.7
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: test-unit
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - "~>"
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: 3.6.8
|
|
101
|
+
type: :development
|
|
102
|
+
prerelease: false
|
|
103
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - "~>"
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: 3.6.8
|
|
108
|
+
description: Helps convert hashes to immutable objects.
|
|
109
|
+
executables: []
|
|
110
|
+
extensions: []
|
|
111
|
+
extra_rdoc_files: []
|
|
112
|
+
files:
|
|
113
|
+
- lib/optify-from_hash.rb
|
|
114
|
+
- lib/optify_from_hash/from_hashable.rb
|
|
115
|
+
- rbi/optify_from_hash.rbi
|
|
116
|
+
- sig/optify_from_hash.rbs
|
|
117
|
+
homepage: https://github.com/juharris/optify
|
|
118
|
+
licenses:
|
|
119
|
+
- MIT
|
|
120
|
+
metadata:
|
|
121
|
+
bug_tracker_uri: https://github.com/juharris/optify/issues
|
|
122
|
+
source_code_uri: https://github.com/juharris/optify
|
|
123
|
+
rdoc_options: []
|
|
124
|
+
require_paths:
|
|
125
|
+
- lib
|
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '3.0'
|
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - ">="
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: '0'
|
|
136
|
+
requirements: []
|
|
137
|
+
rubygems_version: 3.7.2
|
|
138
|
+
specification_version: 4
|
|
139
|
+
summary: Utilities for converting hashes to immutable objects.
|
|
140
|
+
test_files: []
|