mixture 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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