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 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,4 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require_relative 'optify_from_hash/from_hashable'
@@ -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: []