taipo 1.0.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.
@@ -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: []