taipo 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,200 @@
1
+ require 'taipo/exceptions'
2
+ require 'taipo/type_element/child_type'
3
+ require 'taipo/type_element/constraint'
4
+
5
+ module Taipo
6
+
7
+ # An element representing a type (including children and constraints)
8
+ #
9
+ # @since 1.0.0
10
+ # @api private
11
+ class TypeElement
12
+
13
+ # The name of the element
14
+ #
15
+ # @since 1.0.0
16
+ # @api private
17
+ attr_accessor :name
18
+
19
+ # The child type collection for this element
20
+ #
21
+ # @since 1.0.0
22
+ # @api private
23
+ attr_accessor :child_type
24
+
25
+ # The constraint collection for this element
26
+ #
27
+ # @since 1.0.0
28
+ # @api private
29
+ attr_reader :constraints
30
+
31
+ # Initialize a new type element
32
+ #
33
+ # @param name [String] the name of this type
34
+ # @param child_type [Taipo::TypeElement::ChildType|NilClass] the child type
35
+ # collection for this element
36
+ # @param constraints [Array<Taipo::TypeElement::Constraints>|NilClass] an
37
+ # array of constraints for this element
38
+ #
39
+ # @raise [::TypeError] if +name+, +child_type+ or +constraints+ was of the
40
+ # wrong type
41
+ # @raise [::ArgumentError] if +name+, +child_type+ or +constraints+ was
42
+ # blank or empty, or +child_type+ or +constraints+ was non-nil and this
43
+ # is a duck type (ie. a method the type responds to)
44
+ #
45
+ # @since 1.0.0
46
+ # @api private
47
+ def initialize(name:, child_type: nil, constraints: nil)
48
+ msg = 'Argument name was not a String.'
49
+ raise ::TypeError, msg unless name.is_a? String
50
+ msg = 'Argument name was an empty string.'
51
+ raise ::ArgumentError, msg if name.empty?
52
+ msg = 'Argument child_type was not Taipo::TypeElement::ChildType.'
53
+ raise ::TypeError, msg unless (
54
+ child_type.nil? ||
55
+ child_type.is_a?(Taipo::TypeElement::ChildType)
56
+ )
57
+ msg = 'Argument child_type was empty.'
58
+ raise ::ArgumentError, msg if child_type&.empty?
59
+ msg = 'Argument constraints was not an Array.'
60
+ raise ::TypeError, msg unless (constraints.nil? || constraints.is_a?(Array))
61
+ msg = 'Argument constraints was empty.'
62
+ raise ::ArgumentError, msg if constraints&.empty?
63
+
64
+ if Taipo.instance_method? name
65
+ msg = 'Argument child_type should have been nil.'
66
+ raise ::ArgumentError, msg unless child_type.nil?
67
+ msg = 'Argument constraints should have been nil.'
68
+ raise ::ArgumentError, msg unless constraints.nil?
69
+
70
+ constraints = [
71
+ Taipo::TypeElement::Constraint.new(name: nil, value: name[1..-1])
72
+ ]
73
+ name = 'Object'
74
+ end
75
+ @name = name
76
+ @child_type = child_type
77
+ @constraints = constraints
78
+ end
79
+
80
+ # Compare the element with +comp+
81
+ #
82
+ # @param comp [Taipo::TypeElement] the comparison
83
+ #
84
+ # @return [Boolean] the result
85
+ #
86
+ # @raise [::TypeError] if +comp+ is of the wrong type
87
+ #
88
+ # @since 1.0.0
89
+ # @api private
90
+ def ==(comp)
91
+ msg = 'Object to be compared must be of type Taipo::TypeElement.'
92
+ raise ::TypeError, msg unless comp.is_a? Taipo::TypeElement
93
+
94
+ @name == comp.name && @child_type == comp.child_type
95
+ end
96
+
97
+ # Set the element's constraints to +csts+
98
+ #
99
+ # @param csts [Array<Taipo::TypeElement::Constraint] the constraints
100
+ #
101
+ # @raise [::TypeError] if +csts+ was not an Array
102
+ # @raise [Taipo::SyntaxError] if there are constraints with the same name
103
+ #
104
+ # @since 1.0.0
105
+ # @api private
106
+ def constraints=(csts)
107
+ msg = 'Argument csts was not an Array.'
108
+ raise ::TypeError, msg unless csts.is_a? Array
109
+
110
+ names = Hash.new
111
+ csts.each do |c|
112
+ msg = 'Contraints must have unique names.'
113
+ raise Taipo::SyntaxError, msg if names.key?(c.name)
114
+ if c.name == Taipo::TypeElement::Constraint::METHOD
115
+ names['#' + c.value] = true
116
+ else
117
+ names[c.name] = true
118
+ end
119
+ end
120
+ @constraints = csts
121
+ end
122
+
123
+ # Check if the argument matches the element
124
+ #
125
+ # @param arg [Object] the argument to compare
126
+ #
127
+ # @return [Boolean] the result
128
+ #
129
+ # @raise [Taipo::SyntaxError] if the element's +name+ is not defined (see
130
+ # {#match_class?})
131
+ #
132
+ # @since 1.0.0
133
+ # @api private
134
+ def match?(arg)
135
+ match_class?(arg) && match_constraints?(arg) && match_child_type?(arg)
136
+ end
137
+
138
+ # Check if the class of the argument itself matches this element
139
+ #
140
+ # @param arg [Object] the argument to compare
141
+ #
142
+ # @return [Boolean] the result
143
+ #
144
+ # @raise [Taipo::SyntaxError] if the element's +name+ is not defined
145
+ #
146
+ # @since 1.0.0
147
+ # @api private
148
+ def match_class?(arg)
149
+ if @name == 'Boolean'
150
+ arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
151
+ else
152
+ msg = "Class to match #{@name} is not defined"
153
+ raise Taipo::SyntaxError, msg unless Object.const_defined?(@name)
154
+ arg.is_a? Object.const_get(@name)
155
+ end
156
+ end
157
+
158
+ # Check if the class of the argument's child type matches
159
+ #
160
+ # @param arg [Object] the argument to compare
161
+ #
162
+ # @return [Boolean] the result
163
+ #
164
+ # @since 1.0.0
165
+ # @api private
166
+ def match_child_type?(arg)
167
+ self_childless = @child_type.nil?
168
+ arg_childless = !arg.is_a?(Enumerable) || arg.count == 0
169
+ return true if self_childless
170
+ return false if !self_childless && arg_childless
171
+
172
+ arg.all? do |a|
173
+ if a.is_a?(Array) # The elements of this collection have components
174
+ a.each.with_index.reduce(nil) do |memo,(component,index)|
175
+ result = @child_type[index].any? { |c| c.match? component }
176
+ (memo.nil?) ? result : memo && result
177
+ end
178
+ else # The elements of this collection have no components
179
+ @child_type.first.any? { |c| c.match? a }
180
+ end
181
+ end
182
+ end
183
+
184
+ # Check if the argument fits within the constraints
185
+ #
186
+ # @param arg [Object] the argument to compare
187
+ #
188
+ # @return [Boolean] the result
189
+ #
190
+ # @since 1.0.0
191
+ # @api private
192
+ def match_constraints?(arg)
193
+ return true if @constraints.nil?
194
+
195
+ @constraints.all? do |c|
196
+ c.constrain?(arg)
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,21 @@
1
+ module Taipo
2
+ class TypeElement
3
+
4
+ # A collection of Taipo::TypeElement
5
+ # @since 1.0.0
6
+ # @api private
7
+ class ChildType < Array
8
+
9
+ # Initialize a new collection
10
+ #
11
+ # @param components [Array<Taipo::TypeElement>] the components that will make up the ChildType
12
+ #
13
+ # @since 1.0.0
14
+ # @api private
15
+ def initialize(components = nil)
16
+ components.each { |c| self.push c } unless components.nil?
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,152 @@
1
+ module Taipo
2
+ class TypeElement
3
+
4
+ # A constraint on a type
5
+ #
6
+ # @since 1.0.0
7
+ # @api private
8
+ class Constraint
9
+
10
+ # The identifier for an instance method
11
+ #
12
+ # @since 1.0.0
13
+ # @api private
14
+ METHOD = '#'
15
+
16
+ # The name of the constraint
17
+ #
18
+ # @since 1.0.0
19
+ # @api private
20
+ attr_accessor :name
21
+
22
+ # The value of the constraint
23
+ #
24
+ # @since 1.0.0
25
+ # @api private
26
+ attr_reader :value
27
+
28
+ # Initialize a new constraint
29
+ #
30
+ # @param name [String|NilClass] the name of the constraint (if nil, this
31
+ # is an instance method)
32
+ # @param value [String|NilClass] the value of the constraint (sometimes a
33
+ # Constraint is initialized before the value is known)
34
+ #
35
+ # @raise [::TypeError] if +name+ or +value+ were of the wrong type, or
36
+ # +value+ was not of the correct type for the type of constraint
37
+ # @raise [::ArgumentError] if +name+ was blank
38
+ #
39
+ # @since 1.0.0
40
+ # @api private
41
+ def initialize(name: nil, value: nil)
42
+ msg = 'Argument name was not nil or a String.'
43
+ raise ::TypeError, msg unless name.nil? || name.is_a?(String)
44
+ msg = 'Argument name was an empty string.'
45
+ raise ::ArgumentError, msg if name&.empty?
46
+
47
+ @name = (name.nil?) ? Constraint::METHOD : name
48
+ @value = self.parse_value value
49
+ end
50
+
51
+ # Check if +arg+ is within this constraint
52
+ #
53
+ # @param arg [Object] the object to check
54
+ #
55
+ # @return [Boolean] the result
56
+ #
57
+ # @since 1.0.0
58
+ # @api private
59
+ def constrain?(arg)
60
+ case @name
61
+ when Constraint::METHOD
62
+ arg.respond_to? @value
63
+ when 'format'
64
+ arg.is_a?(String) && arg =~ @value
65
+ when 'len'
66
+ arg.respond_to?('size') && arg.size == @value
67
+ when 'max'
68
+ if arg.is_a? Numeric
69
+ arg <= @value
70
+ else
71
+ arg.respond_to?('size') && arg.size <= @value
72
+ end
73
+ when 'min'
74
+ if arg.is_a? Numeric
75
+ arg <= @value
76
+ else
77
+ arg.respond_to?('size') && arg.size >= @value
78
+ end
79
+ when 'val'
80
+ if @value[0] == '"' && @value[-1] == '"'
81
+ arg.to_s == @value.slice(1..-2)
82
+ else
83
+ arg.to_s == @value
84
+ end
85
+ end
86
+ end
87
+
88
+ # Parse +v+ and convert to the appropriate form if necessary
89
+ #
90
+ # @param v [Object] the value
91
+ #
92
+ # @raise [::TypeError] if the value is not appropriate for this type of
93
+ # constraint
94
+ #
95
+ # @since 1.0.0
96
+ # @api private
97
+ def parse_value(v)
98
+ return nil if v == nil
99
+
100
+ case @name
101
+ when Constraint::METHOD
102
+ v
103
+ when 'format'
104
+ return v if v.is_a? Regexp
105
+ msg = 'The value cannot be cast to a regular expression.'
106
+ raise ::TypeError, msg unless v[0] == '/' && v[-1] == '/'
107
+ Regexp.new v[1, v.length-2]
108
+ when 'len', 'max', 'min'
109
+ return v if v.is_a? Integer
110
+ msg = 'The value cannot be cast to an Integer.'
111
+ raise ::TypeError, msg unless v == v.to_i.to_s
112
+ v.to_i
113
+ when 'val'
114
+ v
115
+ end
116
+ end
117
+
118
+ # Return the String representation of this constraint
119
+ #
120
+ # @return [String] the String representation
121
+ #
122
+ # @since 1.0.0
123
+ # @api private
124
+ def to_s
125
+ name_string = (@name == Constraint::METHOD) ? '#' : @name + ':'
126
+ value_string = case @name
127
+ when Constraint::METHOD
128
+ @value
129
+ when 'format'
130
+ @value.inspect
131
+ when 'len', 'max', 'min', 'val'
132
+ @value.to_s
133
+ end
134
+ name_string + value_string
135
+ end
136
+
137
+ # Set +v+ to be the value for this constraint
138
+ #
139
+ # @param v [Object] the value to set (this will be parsed using
140
+ # {#parse_value})
141
+ #
142
+ # @raise [::TypeError] if the value is not appropriate for this type of
143
+ # constraint
144
+ #
145
+ # @since 1.0.0
146
+ # @api private
147
+ def value=(v)
148
+ @value = self.parse_value v
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,3 @@
1
+ module Taipo
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'taipo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "taipo"
8
+ spec.version = Taipo::VERSION
9
+ spec.authors = ["Michael Camilleri"]
10
+ spec.email = ["dev@inqk.net"]
11
+
12
+ spec.summary = %q{A simple library for checking the types of variables.}
13
+ spec.description = %q{Taipo provides a simple way to check your variables are what you think they are. With an easy to use syntax you can call a single method and pass expressive type definitions.}
14
+ spec.homepage = "https://github.com/pyrmont/taipo/"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.12"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest", "~> 5.10.3"
24
+ spec.add_development_dependency "minitest-reporters", "~> 1.1.19"
25
+ spec.add_development_dependency "shoulda-context", "~> 1.2.0"
26
+ spec.add_development_dependency "yard", "~> 0.9.12"
27
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: taipo
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Camilleri
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 5.10.3
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 5.10.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-reporters
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.1.19
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.1.19
69
+ - !ruby/object:Gem::Dependency
70
+ name: shoulda-context
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.2.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.2.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.9.12
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.9.12
97
+ description: Taipo provides a simple way to check your variables are what you think
98
+ they are. With an easy to use syntax you can call a single method and pass expressive
99
+ type definitions.
100
+ email:
101
+ - dev@inqk.net
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - Gemfile
108
+ - LICENSE.md
109
+ - README.md
110
+ - Rakefile
111
+ - lib/taipo.rb
112
+ - lib/taipo/cache.rb
113
+ - lib/taipo/check.rb
114
+ - lib/taipo/exceptions.rb
115
+ - lib/taipo/exceptions/name_error.rb
116
+ - lib/taipo/exceptions/syntax_error.rb
117
+ - lib/taipo/exceptions/type_error.rb
118
+ - lib/taipo/parser.rb
119
+ - lib/taipo/parser/syntax_state.rb
120
+ - lib/taipo/parser/validater.rb
121
+ - lib/taipo/type_element.rb
122
+ - lib/taipo/type_element/child_type.rb
123
+ - lib/taipo/type_element/constraint.rb
124
+ - lib/taipo/version.rb
125
+ - taipo.gemspec
126
+ homepage: https://github.com/pyrmont/taipo/
127
+ licenses: []
128
+ metadata:
129
+ allowed_push_host: https://rubygems.org
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubyforge_project:
146
+ rubygems_version: 2.6.13
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: A simple library for checking the types of variables.
150
+ test_files: []