mixture 0.2.0 → 0.3.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/Gemfile +5 -1
  4. data/Gemfile.lock +3 -1
  5. data/README.md +25 -2
  6. data/lib/mixture/attribute.rb +6 -3
  7. data/lib/mixture/coerce/array.rb +20 -5
  8. data/lib/mixture/coerce/base.rb +62 -26
  9. data/lib/mixture/coerce/class.rb +12 -0
  10. data/lib/mixture/coerce/date.rb +10 -10
  11. data/lib/mixture/coerce/datetime.rb +10 -10
  12. data/lib/mixture/coerce/float.rb +9 -9
  13. data/lib/mixture/coerce/hash.rb +10 -4
  14. data/lib/mixture/coerce/integer.rb +9 -9
  15. data/lib/mixture/coerce/nil.rb +10 -10
  16. data/lib/mixture/coerce/object.rb +13 -13
  17. data/lib/mixture/coerce/rational.rb +9 -9
  18. data/lib/mixture/coerce/set.rb +12 -4
  19. data/lib/mixture/coerce/string.rb +9 -9
  20. data/lib/mixture/coerce/symbol.rb +4 -4
  21. data/lib/mixture/coerce/time.rb +10 -9
  22. data/lib/mixture/coerce.rb +28 -8
  23. data/lib/mixture/extensions/coercable.rb +2 -10
  24. data/lib/mixture/extensions.rb +15 -9
  25. data/lib/mixture/types/access.rb +45 -0
  26. data/lib/mixture/types/array.rb +13 -0
  27. data/lib/mixture/types/boolean.rb +20 -0
  28. data/lib/mixture/types/class.rb +16 -0
  29. data/lib/mixture/types/collection.rb +14 -0
  30. data/lib/mixture/types/date.rb +13 -0
  31. data/lib/mixture/types/datetime.rb +13 -0
  32. data/lib/mixture/types/enumerable.rb +14 -0
  33. data/lib/mixture/types/float.rb +12 -0
  34. data/lib/mixture/types/hash.rb +15 -0
  35. data/lib/mixture/types/integer.rb +12 -0
  36. data/lib/mixture/types/nil.rb +11 -0
  37. data/lib/mixture/types/numeric.rb +12 -0
  38. data/lib/mixture/types/object.rb +24 -0
  39. data/lib/mixture/types/rational.rb +13 -0
  40. data/lib/mixture/types/set.rb +16 -0
  41. data/lib/mixture/types/string.rb +12 -0
  42. data/lib/mixture/types/symbol.rb +14 -0
  43. data/lib/mixture/types/time.rb +13 -0
  44. data/lib/mixture/types/type.rb +171 -0
  45. data/lib/mixture/types.rb +110 -0
  46. data/lib/mixture/version.rb +1 -1
  47. data/lib/mixture.rb +22 -2
  48. data/mixture.gemspec +2 -3
  49. metadata +31 -37
  50. data/lib/mixture/type.rb +0 -188
@@ -0,0 +1,110 @@
1
+ # encoding: utf-8
2
+
3
+ require "thread_safe"
4
+
5
+ module Mixture
6
+ # Contains information about types.
7
+ module Types
8
+ # A list of all of the types (non-anonymous) that are known.
9
+ #
10
+ # @return [Array<Class>]
11
+ def self.types
12
+ @types ||= ThreadSafe::Array.new
13
+ end
14
+
15
+ # A list of the mappings that all types have. This is used
16
+ # primarily to map a type's symbol name to its type (e.g.
17
+ # `:string` to `String`). This is also used for boolean mapping.
18
+ #
19
+ # @return [Hash{Symbol => Mixture::Types::Type}]
20
+ def self.mappings
21
+ ::Hash[types.flat_map do |type|
22
+ type.mappings.map do |name|
23
+ [name, type]
24
+ end
25
+ end]
26
+ end
27
+
28
+ # Infers an object's type. It first checks the mappings to see if
29
+ # the object given is in the mappings; if it's not, it checks if
30
+ # it is a class. If it is a class, it passes it over to
31
+ # {.infer_class}; otherwise, it passes it over to {.infer_type}.
32
+ #
33
+ # @see .mappings
34
+ # @see .infer_class
35
+ # @see .infer_type
36
+ # @param object [Object] The object to infer the type of.
37
+ # @return [Mixture::Types::Type] The type of the object.
38
+ def self.infer(object)
39
+ mappings.fetch(object) do
40
+ if object.is_a?(::Class)
41
+ infer_class(object)
42
+ else
43
+ infer_type(object)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Infers the class of the given object. If the object is a type,
49
+ # it just returns the object. Otherwise, it searches types for a
50
+ # primitive that matches the object. If one is found, it is
51
+ # returned; otherwise, a new `Class` type is created using
52
+ # {Class.[]}. This is primarily used for user-defined classes.
53
+ #
54
+ # @example Creating a type for a user-defined class.
55
+ # class MyClass; end
56
+ # Mixture::Types.infer(MyClass) # => Mixture::Types::Class[MyClass]
57
+ # @api private
58
+ # @see Class.[]
59
+ # @param object [Class] The object to infer the type of.
60
+ # @return [Mixture::Types::Type] The type of the object.
61
+ def self.infer_class(object)
62
+ return object if object <= Type
63
+ types.find { |type| type.options[:primitive] == object } || Class[object]
64
+ end
65
+
66
+ # Infers the type of the object. If the object is an array or set,
67
+ # it returns an {Array} or {Set} type with the object's first
68
+ # element's type as the member type. Otherwise, it tries to find
69
+ # a type that matches the object using {Type.matches?}. This will
70
+ # almost always return a type, if not {Object}.
71
+ #
72
+ # @note This may not return a type if the object is a
73
+ # {BasicObject}. This is because of the constraints on the
74
+ # {Object} type.
75
+ # @example Infers the type of an array of elements.
76
+ # Mixture::Types.infer([1])
77
+ # # => Mixture::Types::Array[Mixture::Types::Integer]
78
+ # @api private
79
+ # @param object [Object] The object to infer.
80
+ # @return [Mixture::Types::Type] The inferred type.
81
+ def self.infer_type(object)
82
+ case object
83
+ when ::Array then Array[infer(object.first)]
84
+ when ::Set then Set[infer(object.first)]
85
+ else types.reverse.find { |type| type.matches?(object) }
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ require "mixture/types/type"
92
+ require "mixture/types/access"
93
+ require "mixture/types/object"
94
+ require "mixture/types/enumerable"
95
+ require "mixture/types/collection"
96
+ require "mixture/types/class"
97
+ require "mixture/types/boolean"
98
+ require "mixture/types/numeric"
99
+ require "mixture/types/array"
100
+ require "mixture/types/date"
101
+ require "mixture/types/datetime"
102
+ require "mixture/types/float"
103
+ require "mixture/types/hash"
104
+ require "mixture/types/integer"
105
+ require "mixture/types/nil"
106
+ require "mixture/types/rational"
107
+ require "mixture/types/set"
108
+ require "mixture/types/string"
109
+ require "mixture/types/symbol"
110
+ require "mixture/types/time"
@@ -5,5 +5,5 @@ module Mixture
5
5
  # The current version of Mixture.
6
6
  #
7
7
  # @return [String]
8
- VERSION = "0.2.0"
8
+ VERSION = "0.3.0"
9
9
  end
data/lib/mixture.rb CHANGED
@@ -9,6 +9,16 @@ module Mixture
9
9
  # An undefined value. This is used in place so that we can be sure
10
10
  # that an argument wasn't passed.
11
11
  #
12
+ # @example As a placeholder.
13
+ # def self.constraint(value = Undefined, &block)
14
+ # if value != Undefined
15
+ # constraints << value
16
+ # elsif block_given?
17
+ # constraints << block
18
+ # else
19
+ # raise ArgumentError, "Expected an argument or block"
20
+ # end
21
+ # end
12
22
  # @return [Object]
13
23
  Undefined = Object.new.freeze
14
24
 
@@ -17,13 +27,23 @@ module Mixture
17
27
  # @return [Proc{(Object) => Object}]
18
28
  Itself = proc { |value| value }
19
29
 
30
+ # Finalizes all of the Mixture modules.
31
+ #
32
+ # @return [void]
33
+ def self.finalize
34
+ Mixture::Coerce.finalize
35
+ Mixture::Extensions.finalize
36
+ nil
37
+ end
38
+
20
39
  require "mixture/version"
21
40
  require "mixture/errors"
22
- require "mixture/type"
41
+ require "mixture/types"
23
42
  require "mixture/attribute"
24
43
  require "mixture/attribute_list"
25
44
  require "mixture/coerce"
26
45
  require "mixture/validate"
27
46
  require "mixture/extensions"
28
- require "mixture/type"
47
+
48
+ finalize
29
49
  end
data/mixture.gemspec CHANGED
@@ -25,7 +25,6 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "bundler"
26
26
  spec.add_development_dependency "rake"
27
27
  spec.add_development_dependency "rspec"
28
- spec.add_development_dependency "pry"
29
- spec.add_development_dependency "coveralls"
30
- spec.add_development_dependency "rubocop"
28
+
29
+ spec.add_dependency "thread_safe", "~> 0.3"
31
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixture
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Rodi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-10 00:00:00.000000000 Z
11
+ date: 2015-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -53,47 +53,19 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: pry
56
+ name: thread_safe
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
61
+ version: '0.3'
62
+ type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: coveralls
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
66
+ - - "~>"
74
67
  - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rubocop
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
68
+ version: '0.3'
97
69
  description: Handle validation, coercion, and attributes.
98
70
  email:
99
71
  - redjazz96@gmail.com
@@ -105,6 +77,7 @@ files:
105
77
  - ".rspec"
106
78
  - ".rubocop.yml"
107
79
  - ".travis.yml"
80
+ - ".yardopts"
108
81
  - CODE_OF_CONDUCT.md
109
82
  - Gemfile
110
83
  - Gemfile.lock
@@ -117,6 +90,7 @@ files:
117
90
  - lib/mixture/coerce.rb
118
91
  - lib/mixture/coerce/array.rb
119
92
  - lib/mixture/coerce/base.rb
93
+ - lib/mixture/coerce/class.rb
120
94
  - lib/mixture/coerce/date.rb
121
95
  - lib/mixture/coerce/datetime.rb
122
96
  - lib/mixture/coerce/float.rb
@@ -136,7 +110,27 @@ files:
136
110
  - lib/mixture/extensions/hashable.rb
137
111
  - lib/mixture/extensions/validatable.rb
138
112
  - lib/mixture/model.rb
139
- - lib/mixture/type.rb
113
+ - lib/mixture/types.rb
114
+ - lib/mixture/types/access.rb
115
+ - lib/mixture/types/array.rb
116
+ - lib/mixture/types/boolean.rb
117
+ - lib/mixture/types/class.rb
118
+ - lib/mixture/types/collection.rb
119
+ - lib/mixture/types/date.rb
120
+ - lib/mixture/types/datetime.rb
121
+ - lib/mixture/types/enumerable.rb
122
+ - lib/mixture/types/float.rb
123
+ - lib/mixture/types/hash.rb
124
+ - lib/mixture/types/integer.rb
125
+ - lib/mixture/types/nil.rb
126
+ - lib/mixture/types/numeric.rb
127
+ - lib/mixture/types/object.rb
128
+ - lib/mixture/types/rational.rb
129
+ - lib/mixture/types/set.rb
130
+ - lib/mixture/types/string.rb
131
+ - lib/mixture/types/symbol.rb
132
+ - lib/mixture/types/time.rb
133
+ - lib/mixture/types/type.rb
140
134
  - lib/mixture/validate.rb
141
135
  - lib/mixture/validate/base.rb
142
136
  - lib/mixture/validate/exclusion.rb
data/lib/mixture/type.rb DELETED
@@ -1,188 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require "forwardable"
4
-
5
- module Mixture
6
- # A type. This can be represented as a constant. This is normally
7
- # anything that inherits `Class`. One instance per type.
8
- class Type
9
- extend Forwardable
10
- @instances = {}
11
- # A class to represent the Boolean type (i.e. true, false), since
12
- # ruby doesn't have a Boolean class.
13
- #
14
- # @return [Class]
15
- BooleanClass = Class.new
16
-
17
- # Ancestors that two classes might have in common with each other.
18
- #
19
- # @return [Array<Module, Class>]
20
- COMMON_ANCESTORS = BooleanClass.ancestors - [BooleanClass]
21
-
22
- # The builtin types of Ruby. These are initialized as types
23
- # before anything else happens.
24
- #
25
- # @return [Array<Symbol>]
26
- BUILTIN_TYPES = %w(
27
- Object Array Hash Integer Rational Float Set String Symbol
28
- Time Date DateTime
29
- ).map(&:intern).freeze
30
-
31
- # The aliases for types. This overrides any other possible
32
- # inferences that this class can make. For example, `true` and
33
- # `false` are automatically mapped to `BooleanClass`.
34
- #
35
- # @return [Hash{Object, Symbol => Class}]
36
- TYPE_ALIASES = {
37
- true => BooleanClass,
38
- false => BooleanClass,
39
- nil => NilClass,
40
- nil: NilClass,
41
- bool: BooleanClass,
42
- boolean: BooleanClass,
43
- str: ::String,
44
- string: ::String,
45
- int: ::Integer,
46
- integer: ::Integer,
47
- rational: ::Rational,
48
- float: ::Float,
49
- array: ::Array,
50
- set: ::Set,
51
- symbol: ::Symbol,
52
- time: ::Time,
53
- date_time: ::DateTime,
54
- date: ::Date
55
- }.freeze
56
-
57
- # Protect our class from being initialized by accident by anyone
58
- # who doesn't know what they're doing. They would have to try
59
- # really hard to initialize this class, and in which case, that
60
- # is acceptable.
61
- private_class_method :new
62
-
63
- # Returns a {Type} from a given class. It assumes that the type
64
- # given is a class, and passes it to `#new` - which will error if
65
- # it isn't.
66
- #
67
- # @param type [Class] A type.
68
- # @return [Mixture::Type]
69
- def self.from(type)
70
- @instances.fetch(type) do
71
- @instances[type] = new(type)
72
- end
73
- end
74
-
75
- # (see .from)
76
- def self.[](type)
77
- from(type)
78
- end
79
-
80
- # Determines the best type that represents the given value. If
81
- # the given type is listed in {TYPE_ALIASES}, it uses the alias
82
- # value for a lookup. If the given type is a {Type}, it returns
83
- # the type. If the given type is a `Class`, it uses {.infer_class}.
84
- # Otherwise, it uses {.infer_class} on the type's class.
85
- #
86
- # @example
87
- # Mixture::Type.infer(Integer) # => Mixture::Type::Integer
88
- #
89
- # @example
90
- # Mixture::Type.infer(1) # => Mixture::Type::Integer
91
- #
92
- # @example
93
- # Mixture::Type.infer(MyClass) # => Mixture::Type[MyClass]
94
- #
95
- # @example
96
- # Mixture::Type.infer(Object.new) # => Mixture::Type::Object
97
- #
98
- # @param value [Object] The value to infer.
99
- # @return [Mixture::Type]
100
- def self.infer(value)
101
- case
102
- when TYPE_ALIASES.key?(value) then from(TYPE_ALIASES[value])
103
- when value.is_a?(Type) then value
104
- when value.is_a?(Class) then infer_class(value)
105
- else infer_class(value.class)
106
- end
107
- end
108
-
109
- # Infer a classes' type. If the class is a type, it returns the
110
- # type. Otherwise, it checks the most basic ancestors (most basic
111
- # ancestors being any ancestors not shared with a new class) for
112
- # any classes that have a {Type}; if there are none, it creates a
113
- # new type from the class.
114
- #
115
- # @param klass [Class] The class to infer type from.
116
- # @return [Mixture::Type]
117
- def self.infer_class(klass)
118
- if klass.is_a?(Type)
119
- klass
120
- else
121
- basic_ancestors = klass.ancestors - COMMON_ANCESTORS
122
- from(basic_ancestors.find { |a| @instances.key?(a) } || klass)
123
- end
124
- end
125
-
126
- # Loads the builtin types. This includes `Boolean` and `Nil`.
127
- #
128
- # @see BUILTIN_TYPES
129
- # @see BooleanClass
130
- # @return [void]
131
- def self.load
132
- BUILTIN_TYPES.each do |sym|
133
- const_set(sym, from(::Object.const_get(sym)))
134
- end
135
-
136
- @instances[BooleanClass] = new(BooleanClass, name: "Boolean")
137
- const_set("Boolean", @instances[BooleanClass])
138
- @instances[NilClass] = new(NilClass, name: "Nil")
139
- const_set("Nil", @instances[NilClass])
140
- end
141
-
142
- # The name of the type. If this wasn't provided upon
143
- # initialization, it is guessed to be the class's name, which is
144
- # normally good enough.
145
- #
146
- # @return [String]
147
- attr_reader :name
148
-
149
- # Initialize the type. The class given _must_ be a class;
150
- # otherwise, it will error. A name can be provided as an option.
151
- #
152
- # @param type [Class] The type to create.
153
- # @param options [Hash{Symbol => Object}] The options.
154
- # @option options [String] :name The name to provide the type
155
- # with. If this is not provided, it uses the class name.
156
- def initialize(type, options = {})
157
- fail ArgumentError, "Expected a Class, got #{type.class}" unless
158
- type.is_a?(Class)
159
- @type = type
160
- @name = options.fetch(:name, @type.name)
161
- end
162
-
163
- # Creates a string representation of the type. This normally has
164
- # the format `Mixture::Type(Class)`, where `Class` is the class.
165
- #
166
- # @return [String]
167
- def to_s
168
- "#{self.class.name}(#{@name})"
169
- end
170
- alias_method :inspect, :to_s
171
-
172
- # Creates a `:to_` method name for the type.
173
- #
174
- # @example
175
- # Mixture::Type[Array].method_name # => :to_array
176
- def method_name
177
- @_method_name ||= begin
178
- body = name
179
- .gsub(/^([A-Z])/) { |m| m.downcase }
180
- .gsub(/::([A-Z])/) { |_, m| "_#{m.downcase}" }
181
- .gsub(/([A-Z])/) { |m| "_#{m.downcase}" }
182
- :"to_#{body}"
183
- end
184
- end
185
- end
186
- end
187
-
188
- Mixture::Type.load