axiom-types 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.
Files changed (63) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +37 -0
  3. data/.rspec +4 -0
  4. data/.rvmrc +1 -0
  5. data/.travis.yml +35 -0
  6. data/.yardopts +4 -0
  7. data/Gemfile +10 -0
  8. data/Gemfile.devtools +62 -0
  9. data/Guardfile +24 -0
  10. data/LICENSE +20 -0
  11. data/README.md +76 -0
  12. data/Rakefile +6 -0
  13. data/TODO +25 -0
  14. data/axiom-types.gemspec +26 -0
  15. data/config/flay.yml +3 -0
  16. data/config/flog.yml +2 -0
  17. data/config/mutant.yml +3 -0
  18. data/config/reek.yml +100 -0
  19. data/config/roodi.yml +16 -0
  20. data/config/yardstick.yml +2 -0
  21. data/lib/axiom-types.rb +3 -0
  22. data/lib/axiom/types.rb +65 -0
  23. data/lib/axiom/types/array.rb +13 -0
  24. data/lib/axiom/types/boolean.rb +14 -0
  25. data/lib/axiom/types/class.rb +13 -0
  26. data/lib/axiom/types/date.rb +18 -0
  27. data/lib/axiom/types/date_time.rb +21 -0
  28. data/lib/axiom/types/decimal.rb +13 -0
  29. data/lib/axiom/types/encodable.rb +75 -0
  30. data/lib/axiom/types/float.rb +13 -0
  31. data/lib/axiom/types/hash.rb +45 -0
  32. data/lib/axiom/types/integer.rb +13 -0
  33. data/lib/axiom/types/length_comparable.rb +51 -0
  34. data/lib/axiom/types/numeric.rb +13 -0
  35. data/lib/axiom/types/object.rb +36 -0
  36. data/lib/axiom/types/set.rb +13 -0
  37. data/lib/axiom/types/string.rb +18 -0
  38. data/lib/axiom/types/support/options.rb +129 -0
  39. data/lib/axiom/types/symbol.rb +18 -0
  40. data/lib/axiom/types/time.rb +22 -0
  41. data/lib/axiom/types/type.rb +136 -0
  42. data/lib/axiom/types/value_comparable.rb +49 -0
  43. data/lib/axiom/types/version.rb +10 -0
  44. data/spec/spec_helper.rb +37 -0
  45. data/spec/unit/axiom/types/class_methods/finalize_spec.rb +22 -0
  46. data/spec/unit/axiom/types/encodable/class_methods/extended_spec.rb +31 -0
  47. data/spec/unit/axiom/types/encodable/finalize_spec.rb +55 -0
  48. data/spec/unit/axiom/types/hash/class_methods/finalize_spec.rb +55 -0
  49. data/spec/unit/axiom/types/length_comparable/class_methods/extended_spec.rb +32 -0
  50. data/spec/unit/axiom/types/length_comparable/finalize_spec.rb +32 -0
  51. data/spec/unit/axiom/types/object/class_methods/coercion_method_spec.rb +27 -0
  52. data/spec/unit/axiom/types/object/class_methods/finalize_spec.rb +29 -0
  53. data/spec/unit/axiom/types/object/class_methods/primitive_spec.rb +27 -0
  54. data/spec/unit/axiom/types/options/accept_options_spec.rb +98 -0
  55. data/spec/unit/axiom/types/options/inherited_spec.rb +38 -0
  56. data/spec/unit/axiom/types/type/class_methods/constraint_spec.rb +32 -0
  57. data/spec/unit/axiom/types/type/class_methods/finalize_spec.rb +20 -0
  58. data/spec/unit/axiom/types/type/class_methods/include_predicate_spec.rb +25 -0
  59. data/spec/unit/axiom/types/type/class_methods/includes_spec.rb +45 -0
  60. data/spec/unit/axiom/types/type/class_methods/new_spec.rb +79 -0
  61. data/spec/unit/axiom/types/value_comparable/class_methods/extended_spec.rb +32 -0
  62. data/spec/unit/axiom/types/value_comparable/finalize_spec.rb +32 -0
  63. metadata +215 -0
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module Types
5
+
6
+ # Represents a set type
7
+ class Set < Object
8
+ primitive ::Set
9
+ coercion_method :to_set
10
+
11
+ end # class Set
12
+ end # module Types
13
+ end # module Axiom
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module Types
5
+
6
+ # Represents a string type
7
+ class String < Object
8
+ extend LengthComparable, Encodable
9
+
10
+ primitive ::String
11
+ coercion_method :to_string
12
+
13
+ minimum_length 0
14
+ maximum_length 255
15
+
16
+ end # class String
17
+ end # module Types
18
+ end # module Axiom
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module Types
5
+
6
+ # A module that adds class and instance level options
7
+ module Options
8
+
9
+ # Raised when the method is already used
10
+ class ReservedMethodError < ArgumentError; end
11
+
12
+ # Defines which options are valid for a given attribute class
13
+ #
14
+ # @example
15
+ # class MyTypes < Axiom::Types::Object
16
+ # accept_options :foo, :bar
17
+ # end
18
+ #
19
+ # @return [self]
20
+ #
21
+ # @api public
22
+ def accept_options(*new_options)
23
+ (new_options - accepted_options).each do |new_option|
24
+ assert_method_available(new_option)
25
+ add_accepted_option(new_option)
26
+ define_option_method(new_option)
27
+ end
28
+ self
29
+ end
30
+
31
+ protected
32
+
33
+ # Adds new option that an attribute class can accept
34
+ #
35
+ # @param [Symbol] new_option
36
+ # new option to be added
37
+ #
38
+ # @return [self]
39
+ #
40
+ # @api private
41
+ def add_accepted_option(new_option)
42
+ accepted_options << new_option
43
+ descendants.each do |descendant|
44
+ descendant.send(__method__, new_option)
45
+ end
46
+ self
47
+ end
48
+
49
+ private
50
+
51
+ # Adds descendant to descendants array and inherits default options
52
+ #
53
+ # @param [Class] descendant
54
+ #
55
+ # @return [undefined]
56
+ #
57
+ # @api private
58
+ def inherited(descendant)
59
+ super
60
+ options.each do |option, value|
61
+ descendant.add_accepted_option(option).public_send(option, value)
62
+ end
63
+ end
64
+
65
+ # Returns default options hash for a given attribute class
66
+ #
67
+ # @example
68
+ # Axiom::Types::String.options
69
+ # # => {:primitive => String}
70
+ #
71
+ # @return [Hash]
72
+ # a hash of default option values
73
+ #
74
+ # @api private
75
+ def options
76
+ accepted_options.each_with_object({}) do |name, options|
77
+ options[name] = public_send(name)
78
+ end
79
+ end
80
+
81
+ # Returns an array of valid options
82
+ #
83
+ # @example
84
+ # Axiom::Types::String.accepted_options
85
+ # # => [:primitive, :accessor, :reader, :writer]
86
+ #
87
+ # @return [Array]
88
+ # the array of valid option names
89
+ #
90
+ # @api private
91
+ def accepted_options
92
+ @accepted_options ||= []
93
+ end
94
+
95
+ # Assert that the option is not already defined
96
+ #
97
+ # @param [Symbol] name
98
+ #
99
+ # @return [undefined]
100
+ #
101
+ # @raise [ReservedMethodError]
102
+ # raised when the method is already defined
103
+ #
104
+ # @api private
105
+ def assert_method_available(name)
106
+ return unless respond_to?(name)
107
+ raise ReservedMethodError,
108
+ "method named `#{name.inspect}` is already defined"
109
+ end
110
+
111
+ # Adds a reader/writer method for the give option name
112
+ #
113
+ # @param [#to_s] name
114
+ #
115
+ # @return [undefined]
116
+ #
117
+ # @api private
118
+ def define_option_method(name)
119
+ ivar = "@#{name}"
120
+ define_singleton_method(name) do |*args|
121
+ return instance_variable_get(ivar) if args.empty?
122
+ instance_variable_set(ivar, *args)
123
+ self
124
+ end
125
+ end
126
+
127
+ end # module Options
128
+ end # module Types
129
+ end # module Axiom
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module Types
5
+
6
+ # Represents a symbol type
7
+ class Symbol < Object
8
+ extend LengthComparable, Encodable
9
+
10
+ primitive ::Symbol
11
+ coercion_method :to_symbol
12
+
13
+ minimum_length 0
14
+ maximum_length 255
15
+
16
+ end # class Symbol
17
+ end # module Types
18
+ end # module Axiom
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module Types
5
+
6
+ # Represents a date time type
7
+ class Time < Object
8
+ extend ValueComparable
9
+
10
+ # The minimum and maximum seconds for Time on 32-bit systems
11
+ MINIMUM_SECONDS = -0x7FFFFFFF
12
+ MAXIMUM_SECONDS = 0x7FFFFFFF
13
+
14
+ primitive ::Time
15
+ coercion_method :to_time
16
+
17
+ minimum primitive.at(MINIMUM_SECONDS).utc
18
+ maximum primitive.at(MAXIMUM_SECONDS).utc
19
+
20
+ end # class Time
21
+ end # module Types
22
+ end # module Axiom
@@ -0,0 +1,136 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module Types
5
+
6
+ # Abstract base class for every type
7
+ class Type
8
+ extend Options, DescendantsTracker
9
+
10
+ accept_options :constraint
11
+ constraint Tautology
12
+
13
+ # Instantiate a new Axiom::Types::Type subclass
14
+ #
15
+ # @example
16
+ # type = Axiom::Types::Type.new # => Axiom::Types::Type
17
+ #
18
+ # @param [#call] constraint
19
+ # optional constraint for the new type
20
+ #
21
+ # @yield [object]
22
+ #
23
+ # @yieldparam object [Object]
24
+ # test if the object matches the type constraint
25
+ #
26
+ # @yieldreturn [Boolean]
27
+ # true if the object matches the type constraint
28
+ #
29
+ # @return [Class<Axiom::Types::Type>]
30
+ #
31
+ # @api public
32
+ def self.new(constraint = Undefined, &block)
33
+ type = ::Class.new(self, &block)
34
+ type.constraint(constraint)
35
+ type.finalize
36
+ end
37
+
38
+ # Finalize by deep freezing
39
+ #
40
+ # @return [Axiom::Types::Type]
41
+ #
42
+ # @api private
43
+ def self.finalize
44
+ IceNine.deep_freeze(constraint)
45
+ freeze
46
+ end
47
+
48
+ # Test if the object matches the type constraint
49
+ #
50
+ # @example
51
+ # type = Axiom::Types::Integer.new do
52
+ # minimum 1
53
+ # maximum 100
54
+ # end
55
+ #
56
+ # type.include?(1) # => true
57
+ # type.include?(100) # => true
58
+ # type.include?(0) # => false
59
+ # type.include?(101) # => false
60
+ #
61
+ # @param [Object] object
62
+ #
63
+ # @return [Boolean]
64
+ #
65
+ # @api public
66
+ def self.include?(object)
67
+ constraint.call(object)
68
+ end
69
+
70
+ # Add a constraint to the type
71
+ #
72
+ # @example with an argument
73
+ # type.constraint(lambda { |object| object == 42 }
74
+ #
75
+ # @example with a block
76
+ # type.constraint { |object| object == 42 }
77
+ #
78
+ # @example with no arguments
79
+ # type.constraint # => constraint
80
+ #
81
+ # @param [#call] constraint
82
+ # optional constraint
83
+ #
84
+ # @yield [object]
85
+ #
86
+ # @yieldparam object [Object]
87
+ # test if the object matches the type constraint
88
+ #
89
+ # @yieldreturn [Boolean]
90
+ # true if the object matches the type constraint
91
+ #
92
+ # @return [self]
93
+ #
94
+ # @api public
95
+ def self.constraint(constraint = Undefined, &block)
96
+ constraint = block if constraint.equal?(Undefined)
97
+ return @constraint if constraint.nil?
98
+ add_constraint(constraint)
99
+ self
100
+ end
101
+
102
+ # Add a constraint that the object must be included in a set
103
+ #
104
+ # @param [Array<Object>] members
105
+ #
106
+ # @return [undefined]
107
+ #
108
+ # @todo move into a module
109
+ #
110
+ # @api private
111
+ def self.includes(*members)
112
+ set = IceNine.deep_freeze(members.to_set)
113
+ constraint(&set.method(:include?))
114
+ end
115
+
116
+ # Add new constraint to existing constraint, if any
117
+ #
118
+ # @param [#call] constraint
119
+ #
120
+ # @return [undefined]
121
+ #
122
+ # @api private
123
+ def self.add_constraint(constraint)
124
+ current = self.constraint
125
+ @constraint = if current
126
+ lambda { |object| constraint.call(object) && current.call(object) }
127
+ else
128
+ constraint
129
+ end
130
+ end
131
+
132
+ private_class_method :add_constraint
133
+
134
+ end # class Type
135
+ end # module Types
136
+ end # module Axiom
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module Types
5
+
6
+ # Add a minimum and maximum value constraint to a type
7
+ module ValueComparable
8
+
9
+ # Hook called when module is extended
10
+ #
11
+ # Add #minimum and #maximum DSL methods to descendant.
12
+ #
13
+ # @param [Class<Axiom::Types::Type>] descendant
14
+ #
15
+ # @return [undefined]
16
+ #
17
+ # @api private
18
+ def self.extended(descendant)
19
+ super
20
+ descendant.accept_options :minimum, :maximum
21
+ end
22
+
23
+ # Finalize by setting up a value range constraint
24
+ #
25
+ # @return [Axiom::Types::ValueComparable]
26
+ #
27
+ # @api private
28
+ def finalize
29
+ return self if frozen?
30
+ has_value_within_range
31
+ super
32
+ end
33
+
34
+ private
35
+
36
+ # Add a constraint for a value within a range
37
+ #
38
+ # @return [undefined]
39
+ #
40
+ # @todo freeze the minimum and maximum
41
+ #
42
+ # @api private
43
+ def has_value_within_range
44
+ constraint { |object| object.between?(minimum, maximum) }
45
+ end
46
+
47
+ end # module ValueComparable
48
+ end # module Types
49
+ end # module Axiom
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ module Axiom
4
+ module Types
5
+
6
+ # Gem version
7
+ VERSION = '0.0.1'.freeze
8
+
9
+ end # module Types
10
+ end # module Axiom
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ require 'devtools'
4
+
5
+ Devtools.init_spec_helper
6
+
7
+ if ENV['COVERAGE'] == 'true'
8
+ require 'simplecov'
9
+ require 'coveralls'
10
+
11
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
12
+ SimpleCov::Formatter::HTMLFormatter,
13
+ Coveralls::SimpleCov::Formatter
14
+ ]
15
+
16
+ SimpleCov.start do
17
+ command_name 'spec:unit'
18
+ add_filter 'config'
19
+ add_filter 'spec'
20
+ minimum_coverage 100
21
+ end
22
+ end
23
+
24
+ require 'axiom-types'
25
+
26
+ include Axiom::Types
27
+
28
+ # require spec support files and shared behavior
29
+ Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each do |file|
30
+ require file
31
+ end
32
+
33
+ RSpec.configure do |config|
34
+ config.expect_with :rspec do |expect_with|
35
+ expect_with.syntax = :expect
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Axiom::Types, '.finalize' do
6
+ subject { object.finalize }
7
+
8
+ let(:object) { described_class }
9
+ let(:descendants) { [ descendant ] }
10
+ let(:descendant) { mock('descendant').as_null_object }
11
+
12
+ before do
13
+ object::Type.should_receive(:descendants).and_return(descendants)
14
+ end
15
+
16
+ it_should_behave_like 'a command method'
17
+
18
+ it 'finalizes the descendants' do
19
+ descendant.should_receive(:finalize)
20
+ subject
21
+ end
22
+ end