taipo 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/README.md +30 -1
- data/lib/taipo.rb +15 -22
- data/lib/taipo/cache.rb +5 -3
- data/lib/taipo/check.rb +19 -32
- data/lib/taipo/parser.rb +66 -50
- data/lib/taipo/parser/stack.rb +3 -2
- data/lib/taipo/parser/validater.rb +7 -8
- data/lib/taipo/result.rb +102 -0
- data/lib/taipo/type_element/children.rb +0 -2
- data/lib/taipo/type_element/constraints.rb +2 -3
- data/lib/taipo/utilities.rb +80 -1
- data/lib/taipo/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2d4388512ddb3fd352644adc19c2d597617c1ba
|
4
|
+
data.tar.gz: 424f50365c4c88bf5d3d511cf66bfdf59431504c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13da1bffba5a77cc4a1c2040c0fe9d6ef0112c69968c30b89ca72bcff068f303593dbdc6ed276056571ae037a3170c41458e77911f3e0227527ddd9d8cef6efc
|
7
|
+
data.tar.gz: 167bffef5e9d7f24f0af153b3dfbb44d655067f0b1223a1505447e52e9cce84ef00895d60b4eddacdaedf85669296d7c504396a47e274f977288b146a343a9c0
|
data/.travis.yml
CHANGED
@@ -3,7 +3,7 @@ env:
|
|
3
3
|
- CC_TEST_REPORTER_ID=787a2f89b15c637323c7340d65ec17e898ac44480706b4b4122ea040c2a88f1d
|
4
4
|
language: ruby
|
5
5
|
rvm:
|
6
|
-
- 2.4.
|
6
|
+
- 2.4.2
|
7
7
|
before_script:
|
8
8
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
9
9
|
- chmod +x ./cc-test-reporter
|
data/README.md
CHANGED
@@ -22,7 +22,10 @@ Run `gem install taipo` or add `gem 'taipo'` to your `Gemfile`.
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
Taipo
|
25
|
+
Taipo can be used to check variables within a given context or check the return value of a given method:
|
26
|
+
|
27
|
+
* the `Taipo::Check` module provides two methods: `#check` and `#review`:
|
28
|
+
* the `Taipo::Result` module provides one method: `.result`.
|
26
29
|
|
27
30
|
### #check
|
28
31
|
|
@@ -78,6 +81,32 @@ The method `#review` will put the invalid arguments into an array and return tha
|
|
78
81
|
|
79
82
|
[rdr]: http://www.rubydoc.info/gems/taipo/Taipo/Check#review-instance_method
|
80
83
|
|
84
|
+
### .result
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
require 'taipo'
|
88
|
+
|
89
|
+
class Foo
|
90
|
+
include Taipo::Result
|
91
|
+
|
92
|
+
result :add, 'Integer'
|
93
|
+
|
94
|
+
def add(x, y)
|
95
|
+
x + y
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
foo = Foo.new
|
100
|
+
foo.add 4, 5 #=> 9
|
101
|
+
foo.add 'hello', 'world' #=> Taipo::TypeError
|
102
|
+
```
|
103
|
+
|
104
|
+
The method `.result` will raise an exception if the return value of the specified method doesn't match the specified type definition.
|
105
|
+
|
106
|
+
[More information about `.result`][rde] is available in the documentation.
|
107
|
+
|
108
|
+
[rde]: http://www.rubydoc.info/gems/taipo/Taipo/Result/ClassMethods#result-instance_method
|
109
|
+
|
81
110
|
## Syntax
|
82
111
|
|
83
112
|
Type definitions are written as Strings. Type definitions can consist of (1) names, (2) collections, (3) constraints and (4) sums.
|
data/lib/taipo.rb
CHANGED
@@ -1,33 +1,26 @@
|
|
1
1
|
require 'taipo/version'
|
2
2
|
require 'taipo/check'
|
3
|
-
require 'taipo/
|
3
|
+
require 'taipo/result'
|
4
4
|
|
5
5
|
# A library for checking the types of objects
|
6
6
|
#
|
7
|
-
# Taipo is primarily intended as a replacement for
|
8
|
-
#
|
9
|
-
#
|
7
|
+
# Taipo is primarily intended as a replacement for verbose, error-prone guard
|
8
|
+
# statements. With Taipo, a user can ensure that the objects they are working
|
9
|
+
# with conform to expectations.
|
10
10
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# constraints may be specified and sum types are also possible. See
|
17
|
-
# {Taipo::Parser::Validater} for the full syntax.
|
11
|
+
# Taipo consists of two user-facing parts: {Taipo::Check} and {Taipo::Result}.
|
12
|
+
#
|
13
|
+
# * By including the module {Taipo::Check}, a user can call
|
14
|
+
# {Taipo::Check#check} or {Taipo::Check#review} in their methods to
|
15
|
+
# check the types of one or more given variables.
|
18
16
|
#
|
19
|
-
# Taipo
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# {Taipo::TypeElement} instances; and
|
23
|
-
# 3. checking whether the argument's value matches any of the instances of
|
24
|
-
# {Taipo::TypeElement} in the array.
|
17
|
+
# * By including the module {Taipo::Result}, a user can call
|
18
|
+
# {Taipo::Result::ClassMethods#result} in their class definitions to check the
|
19
|
+
# return values of a given method.
|
25
20
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# {Taipo::
|
29
|
-
# {Taipo::Check#review}). If the user does not want to alias, they can set
|
30
|
-
# {Taipo.alias=} to +false+ before including or extending {Taipo::Check}.
|
21
|
+
# Taipo provides a rich syntax to express type definitions. This includes
|
22
|
+
# classes, collections, optionals and duck types. See
|
23
|
+
# {Taipo::Parser::Validater} for the full syntax.
|
31
24
|
#
|
32
25
|
# @since 1.0.0
|
33
26
|
# @see https://github.com/pyrmont/taipo
|
data/lib/taipo/cache.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Taipo
|
2
2
|
|
3
|
-
# A cache of Taipo::TypeElement created from parsed type definitions
|
3
|
+
# A cache of {Taipo::TypeElement} objects created from parsed type definitions
|
4
4
|
#
|
5
5
|
# @since 1.0.0
|
6
6
|
# @api private
|
@@ -12,7 +12,8 @@ module Taipo
|
|
12
12
|
# @api private
|
13
13
|
@@Cache = {}
|
14
14
|
|
15
|
-
# Retrieve the Taipo::TypeElement object described by the type definition
|
15
|
+
# Retrieve the {Taipo::TypeElement} object described by the type definition
|
16
|
+
# from the cache
|
16
17
|
#
|
17
18
|
# @param k [String] the type definition
|
18
19
|
#
|
@@ -25,7 +26,8 @@ module Taipo
|
|
25
26
|
@@Cache[k]
|
26
27
|
end
|
27
28
|
|
28
|
-
# Save the Taipo::TypeElement object described by the type definition in
|
29
|
+
# Save the {Taipo::TypeElement} object described by the type definition in
|
30
|
+
# the cache
|
29
31
|
#
|
30
32
|
# @param k [String] the type definition
|
31
33
|
# @param v [Taipo::TypeElement] the object to be saved
|
data/lib/taipo/check.rb
CHANGED
@@ -1,13 +1,21 @@
|
|
1
|
-
require 'taipo/cache'
|
2
|
-
require 'taipo/exceptions'
|
3
|
-
require 'taipo/parser'
|
4
|
-
require 'taipo/type_elements'
|
5
|
-
require 'taipo/type_element'
|
6
1
|
require 'taipo/utilities'
|
7
2
|
|
8
3
|
module Taipo
|
9
4
|
|
10
|
-
# A
|
5
|
+
# A simple DSL for declaring type checks to run against specified variables
|
6
|
+
#
|
7
|
+
# {Taipo::Check} works by:
|
8
|
+
# 1. extracting the values of the arguments to be checked from a Binding;
|
9
|
+
# 2. transforming the type definitions provided as Strings into an array of
|
10
|
+
# {Taipo::TypeElement} instances; and
|
11
|
+
# 3. checking whether the argument's value matches any of the instances of
|
12
|
+
# {Taipo::TypeElement} in the array.
|
13
|
+
#
|
14
|
+
# As syntactic sugar, the {Taipo::Check} module will by default alias
|
15
|
+
# +Kernel#binding+ with the keyword +types+. This allows the user to call
|
16
|
+
# {Taipo::Check#check} by writing +check types+ (with a similar syntax for
|
17
|
+
# {Taipo::Check#review}). If the user does not want to alias, they can set
|
18
|
+
# {Taipo.alias=} to +false+ before including or extending {Taipo::Check}.
|
11
19
|
#
|
12
20
|
# @since 1.0.0
|
13
21
|
module Check
|
@@ -70,35 +78,14 @@ module Taipo
|
|
70
78
|
raise ::TypeError, msg unless context.is_a? Binding
|
71
79
|
|
72
80
|
checks.reduce(Array.new) do |memo,(k,v)|
|
73
|
-
arg =
|
74
|
-
|
75
|
-
|
76
|
-
context.local_variable_get k
|
77
|
-
else
|
78
|
-
msg = "Argument '#{k}' is not defined."
|
79
|
-
raise Taipo::NameError, msg
|
80
|
-
end
|
81
|
-
|
82
|
-
types = if hit = Taipo::Cache[v]
|
83
|
-
hit
|
84
|
-
else
|
85
|
-
Taipo::Cache[v] = Taipo::Parser.parse v
|
86
|
-
end
|
81
|
+
arg = Taipo::Utilities.extract_variable(name: k,
|
82
|
+
object: self,
|
83
|
+
context: context)
|
87
84
|
|
88
|
-
is_match =
|
85
|
+
is_match = Taipo::Utilities.match? object: arg, definition: v
|
89
86
|
|
90
87
|
unless collect_invalids || is_match
|
91
|
-
|
92
|
-
msg = "Object '#{k}' does not respond to #{v}."
|
93
|
-
elsif Taipo::Utilities.symbol? v
|
94
|
-
msg = "Object '#{k}' is not equal to #{v}."
|
95
|
-
elsif arg.is_a? Enumerable
|
96
|
-
type_def = Taipo::Utilities.object_to_type_def arg
|
97
|
-
msg = "Object '#{k}' is #{type_def} but expected #{v}."
|
98
|
-
else
|
99
|
-
msg = "Object '#{k}' is #{arg.class.name} but expected #{v}."
|
100
|
-
end
|
101
|
-
raise Taipo::TypeError, msg
|
88
|
+
Taipo::Utilities.throw_error object: arg, name: k, definition: v
|
102
89
|
end
|
103
90
|
|
104
91
|
(is_match) ? memo : memo.push(k)
|
data/lib/taipo/parser.rb
CHANGED
@@ -1,12 +1,7 @@
|
|
1
|
-
require 'taipo/
|
1
|
+
require 'taipo/cache'
|
2
2
|
require 'taipo/parser/stack'
|
3
3
|
require 'taipo/parser/validater'
|
4
4
|
require 'taipo/refinements'
|
5
|
-
require 'taipo/type_elements'
|
6
|
-
require 'taipo/type_element'
|
7
|
-
require 'taipo/type_element/children'
|
8
|
-
require 'taipo/type_element/constraints'
|
9
|
-
require 'taipo/type_element/constraint'
|
10
5
|
|
11
6
|
module Taipo
|
12
7
|
|
@@ -19,57 +14,24 @@ module Taipo
|
|
19
14
|
|
20
15
|
# Return a Taipo::TypeElements object based on +str+
|
21
16
|
#
|
17
|
+
# This method acts as a wrapping method to {Taipo::Parser.parse_definition}.
|
18
|
+
# It first checks if the type definition has already been parsed and is in
|
19
|
+
# Taipo's cache.
|
20
|
+
#
|
22
21
|
# @param str [String] a type definition
|
23
22
|
#
|
24
|
-
# @return [Taipo
|
23
|
+
# @return [Taipo::TypeElements] the result
|
25
24
|
#
|
26
25
|
# @raise [::TypeError] if +str+ is not a String
|
27
26
|
# @raise [Taipo::SyntaxError] if +str+ is not a valid type definition
|
28
27
|
#
|
29
28
|
# @since 1.0.0
|
30
29
|
def self.parse(str)
|
31
|
-
Taipo::
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
subject = :implied
|
36
|
-
chars = str.chars
|
37
|
-
content = ''
|
38
|
-
|
39
|
-
while (i < chars.size)
|
40
|
-
reset = true
|
41
|
-
|
42
|
-
case chars[i]
|
43
|
-
when ' '
|
44
|
-
i += 1
|
45
|
-
next
|
46
|
-
when '|'
|
47
|
-
stack = process_sum stack, name: content
|
48
|
-
subject = :implied
|
49
|
-
when '<'
|
50
|
-
stack = process_collection :open, stack, name: content
|
51
|
-
subject = :implied
|
52
|
-
when '>'
|
53
|
-
stack = process_collection :close, stack, name: content
|
54
|
-
subject = :made
|
55
|
-
when ','
|
56
|
-
stack = process_component stack, name: content
|
57
|
-
subject = :implied
|
58
|
-
when '('
|
59
|
-
stack = process_subject stack, name: content, subject: subject
|
60
|
-
stack, i = process_constraints stack, chars: chars, index: i+1
|
61
|
-
else
|
62
|
-
reset = false
|
63
|
-
subject = :unmade
|
64
|
-
end
|
65
|
-
|
66
|
-
content = (reset) ? '' : content + chars[i]
|
67
|
-
i += 1
|
30
|
+
if hit = Taipo::Cache[str]
|
31
|
+
hit
|
32
|
+
else
|
33
|
+
Taipo::Cache[str] = Taipo::Parser.parse_definition str
|
68
34
|
end
|
69
|
-
|
70
|
-
stack = process_end stack, name: content
|
71
|
-
|
72
|
-
stack.result
|
73
35
|
end
|
74
36
|
|
75
37
|
# Check whether the character should be skipped
|
@@ -128,7 +90,7 @@ module Taipo
|
|
128
90
|
content = ''
|
129
91
|
str.each_char do |c|
|
130
92
|
if c == '#' && in_name.nil?
|
131
|
-
name =
|
93
|
+
name = '#'
|
132
94
|
in_name = false
|
133
95
|
elsif c == ':' && in_name.nil?
|
134
96
|
name = 'val'
|
@@ -147,6 +109,60 @@ module Taipo
|
|
147
109
|
return name, value
|
148
110
|
end
|
149
111
|
|
112
|
+
# Return a Taipo::TypeElements object based on +str+
|
113
|
+
#
|
114
|
+
# @param str (see {Taipo::Parser.parse})
|
115
|
+
#
|
116
|
+
# @return (see {Taipo::Parser.parse})
|
117
|
+
#
|
118
|
+
# @raise (see {Taipo::Parser.parse})
|
119
|
+
#
|
120
|
+
# @since 1.5.0
|
121
|
+
# @api private
|
122
|
+
def self.parse_definition(str)
|
123
|
+
Taipo::Parser::Validater.validate str
|
124
|
+
|
125
|
+
stack = Taipo::Parser::Stack.new
|
126
|
+
i = 0
|
127
|
+
subject = :implied
|
128
|
+
chars = str.chars
|
129
|
+
content = ''
|
130
|
+
|
131
|
+
while (i < chars.size)
|
132
|
+
reset = true
|
133
|
+
|
134
|
+
case chars[i]
|
135
|
+
when ' '
|
136
|
+
i += 1
|
137
|
+
next
|
138
|
+
when '|'
|
139
|
+
stack = process_sum stack, name: content
|
140
|
+
subject = :implied
|
141
|
+
when '<'
|
142
|
+
stack = process_collection :open, stack, name: content
|
143
|
+
subject = :implied
|
144
|
+
when '>'
|
145
|
+
stack = process_collection :close, stack, name: content
|
146
|
+
subject = :made
|
147
|
+
when ','
|
148
|
+
stack = process_component stack, name: content
|
149
|
+
subject = :implied
|
150
|
+
when '('
|
151
|
+
stack = process_subject stack, name: content, subject: subject
|
152
|
+
stack, i = process_constraints stack, chars: chars, index: i+1
|
153
|
+
else
|
154
|
+
reset = false
|
155
|
+
subject = :unmade
|
156
|
+
end
|
157
|
+
|
158
|
+
content = (reset) ? '' : content + chars[i]
|
159
|
+
i += 1
|
160
|
+
end
|
161
|
+
|
162
|
+
stack = process_end stack, name: content
|
163
|
+
stack.result
|
164
|
+
end
|
165
|
+
|
150
166
|
# Process a collection
|
151
167
|
#
|
152
168
|
# This method either adds or updates a collection on +stack+ depending on
|
@@ -210,7 +226,7 @@ module Taipo
|
|
210
226
|
# @api private
|
211
227
|
def self.process_constraint(stack, raw:)
|
212
228
|
n, v = parse_constraint raw
|
213
|
-
stack.add_constraint
|
229
|
+
stack.add_constraint name: n, value: v
|
214
230
|
end
|
215
231
|
|
216
232
|
# Process a series of constraints
|
data/lib/taipo/parser/stack.rb
CHANGED
@@ -112,8 +112,9 @@ module Taipo
|
|
112
112
|
#
|
113
113
|
# @since 1.4.0
|
114
114
|
# @api private
|
115
|
-
def add_constraint(
|
116
|
-
self.last.push
|
115
|
+
def add_constraint(name:, value:)
|
116
|
+
self.last.push Taipo::TypeElement::Constraint.new(name: name,
|
117
|
+
value: value)
|
117
118
|
self
|
118
119
|
end
|
119
120
|
|
@@ -18,8 +18,7 @@ module Taipo
|
|
18
18
|
#
|
19
19
|
# The validater does not check whether the name represents a valid name in
|
20
20
|
# the current context nor does it check whether the name complies with
|
21
|
-
# Ruby's requirements for names.
|
22
|
-
# to be raised by {Taipo::Check#check} or {Taipo::Check#review}.
|
21
|
+
# Ruby's requirements for names.
|
23
22
|
#
|
24
23
|
# One special case is where the name is left blank. The validater will
|
25
24
|
# accept this as valid. {Taipo::Parser} will implictly add the name
|
@@ -32,7 +31,7 @@ module Taipo
|
|
32
31
|
#
|
33
32
|
# As noted above, duck types can be specified by using a blank name. Duck
|
34
33
|
# types are really constraints (discussed in further detail below) on the
|
35
|
-
# class Object
|
34
|
+
# class +Object+. While normally constraints need to be enclosed in
|
36
35
|
# parentheses, if there is a blank name and only one method constraint, the
|
37
36
|
# parentheses can be omitted. For defining duck types that respond to
|
38
37
|
# multiple methods, the parentheses are required.
|
@@ -43,10 +42,10 @@ module Taipo
|
|
43
42
|
#
|
44
43
|
# It is possible to specify an 'optional' type by appending a question mark
|
45
44
|
# to the name of the type. This shorthand functions similarly to defining a
|
46
|
-
# sum type with NilClass (the implementation of how optional types are
|
45
|
+
# sum type with +NilClass+ (the implementation of how optional types are
|
47
46
|
# checked is slightly different, however; see {Taipo::TypeElement#match?}).
|
48
47
|
# It is not possible to define an optional duck type. For that, either the
|
49
|
-
# implicit Object class should be specified (and then made optional), or a
|
48
|
+
# implicit +Object+ class should be specified (and then made optional), or a
|
50
49
|
# sum type should be used.
|
51
50
|
#
|
52
51
|
# === Collections
|
@@ -62,8 +61,8 @@ module Taipo
|
|
62
61
|
# definition for the child comes immediately after the opening angle
|
63
62
|
# bracket.
|
64
63
|
#
|
65
|
-
# If +Enumerator#each+ returns multiple values (eg. such as with Hash),
|
66
|
-
# type definition for each value is delimited by a comma. It is optional
|
64
|
+
# If +Enumerator#each+ returns multiple values (eg. such as with +Hash+),
|
65
|
+
# the type definition for each value is delimited by a comma. It is optional
|
67
66
|
# whether a space follows the comma.
|
68
67
|
#
|
69
68
|
# The type definition for a child element can contain all the components of
|
@@ -123,7 +122,7 @@ module Taipo
|
|
123
122
|
# It's possible to approximate the enum idiom available in many languages
|
124
123
|
# by creating a sum type consisting of Symbols. As a convenience, Taipo
|
125
124
|
# parses these values as constraints on the Object class. In other words,
|
126
|
-
#
|
125
|
+
# the +:foo|:bar+ is really +Object(val: :foo)|Object(val: :bar)+.
|
127
126
|
#
|
128
127
|
# @since 1.0.0
|
129
128
|
module Validater
|
data/lib/taipo/result.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'taipo/utilities'
|
2
|
+
|
3
|
+
module Taipo
|
4
|
+
|
5
|
+
# A simple DSL for declaring type checks to run against the return values of
|
6
|
+
# specified instance methods
|
7
|
+
#
|
8
|
+
# {Taipo::Result} works by:
|
9
|
+
# 1. adding the {Taipo::Result::ClassMethods#result} method to the including
|
10
|
+
# class;
|
11
|
+
# 2. prepending a module to the ancestor chain for the including class; and
|
12
|
+
# 3. defining a method on the ancestor with the same name as a particular
|
13
|
+
# instance method on the class and using that method to intercept method
|
14
|
+
# calls and check the return type.
|
15
|
+
#
|
16
|
+
# Because of how {Taipo::Result} makes the +result+ keyword available to
|
17
|
+
# classes, the documentation for this method is in
|
18
|
+
# {Taipo::Result::ClassMethods}.
|
19
|
+
#
|
20
|
+
# @since 1.5.0
|
21
|
+
module Result
|
22
|
+
|
23
|
+
# Add the {Taipo::Result::ClassMethods#result} method to the class including
|
24
|
+
# this module as well as prepend a module to the ancestor chain.
|
25
|
+
#
|
26
|
+
# @param base [Class] the class including this module
|
27
|
+
#
|
28
|
+
# @since 1.5.0
|
29
|
+
# @api private
|
30
|
+
def self.included(base)
|
31
|
+
base.extend ClassMethods
|
32
|
+
module_name = "#{base.class.name}Checker"
|
33
|
+
checker = const_defined?(module_name) ? const_get(module_name) :
|
34
|
+
const_set(module_name, Module.new)
|
35
|
+
base.prepend checker
|
36
|
+
end
|
37
|
+
|
38
|
+
# A helper module for holding the DSL methods
|
39
|
+
#
|
40
|
+
# @since 1.5.0
|
41
|
+
# @api private
|
42
|
+
module ClassMethods
|
43
|
+
|
44
|
+
# The DSL method used to declare a type check on the return value of a
|
45
|
+
# method. The intention is that a user will declare the type of result
|
46
|
+
# expected from a method by calling this method in the body of a class
|
47
|
+
# definition.
|
48
|
+
#
|
49
|
+
# For purposes of readability, the convention is that
|
50
|
+
# {Taipo::Result::ClassMethods#result} will be called near the beginning
|
51
|
+
# of the class definition but this is not a requirement and the user is
|
52
|
+
# free to call the method immediately before or after the relevant method
|
53
|
+
# definition.
|
54
|
+
#
|
55
|
+
# @param method_name [Symbol] the name of the instance method
|
56
|
+
# @param type [String] a type definition
|
57
|
+
#
|
58
|
+
# @since 1.5.0
|
59
|
+
# @api public
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# require 'taipo'
|
63
|
+
#
|
64
|
+
# class A
|
65
|
+
# include Taipo::Result
|
66
|
+
#
|
67
|
+
# result :foo, 'String'
|
68
|
+
# result :bar, 'Integer'
|
69
|
+
#
|
70
|
+
# def foo(arg)
|
71
|
+
# arg.to_s
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# def bar(arg)
|
75
|
+
# arg.to_s
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# a = A.new
|
80
|
+
# a.foo 'Hello world!' #=> "Hello world!"
|
81
|
+
# a.bar 42 #=> Taipo::TypeError
|
82
|
+
def result(method_name, type)
|
83
|
+
base = self
|
84
|
+
checker = const_get "#{base.class.name}Checker"
|
85
|
+
checker.class_eval do
|
86
|
+
define_method(method_name) do |*args, &block|
|
87
|
+
method_return_value = super(*args, &block)
|
88
|
+
if Taipo::Utilities.match?(object: method_return_value,
|
89
|
+
definition: type)
|
90
|
+
method_return_value
|
91
|
+
else
|
92
|
+
Taipo::Utilities.throw_error(object: method_return_value,
|
93
|
+
name: "#{base.name}##{method_name}",
|
94
|
+
definition: type,
|
95
|
+
result: true)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'taipo/type_element/constraint'
|
2
|
-
|
3
1
|
module Taipo
|
4
2
|
class TypeElement
|
5
3
|
|
@@ -11,7 +9,8 @@ module Taipo
|
|
11
9
|
|
12
10
|
# Initialize a new set of {Taipo::TypeElement::Constraint}
|
13
11
|
#
|
14
|
-
# @param
|
12
|
+
# @param constraints [Array<Taipo::TypeElement::Constraint>] the
|
13
|
+
# constraints
|
15
14
|
#
|
16
15
|
# @since 1.4.0
|
17
16
|
# @api private
|
data/lib/taipo/utilities.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'taipo/exceptions'
|
2
|
+
require 'taipo/parser'
|
3
|
+
|
1
4
|
module Taipo
|
2
5
|
|
3
6
|
# Utility methods for Taipo
|
@@ -6,6 +9,29 @@ module Taipo
|
|
6
9
|
# @api private
|
7
10
|
module Utilities
|
8
11
|
|
12
|
+
# Return a named variable from either an object or a binding
|
13
|
+
#
|
14
|
+
# @param name [String] the name of the variable
|
15
|
+
# @param object [Object] the object in which the variable may exist
|
16
|
+
# @param context [Binding] the binding in which the variable may exist
|
17
|
+
#
|
18
|
+
# @return [Object] the variable
|
19
|
+
#
|
20
|
+
# @raise [Taipo::NameError] if no variable with +name+ exists
|
21
|
+
#
|
22
|
+
# @since 1.5.0
|
23
|
+
# @api private
|
24
|
+
def self.extract_variable(name:, object:, context:)
|
25
|
+
if name[0] == '@' && object.instance_variable_defined?(name)
|
26
|
+
object.instance_variable_get name
|
27
|
+
elsif name[0] != '@' && context.local_variable_defined?(name)
|
28
|
+
context.local_variable_get name
|
29
|
+
else
|
30
|
+
msg = "Argument '#{name}' is not defined."
|
31
|
+
raise Taipo::NameError, msg
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
9
35
|
# Check if a string is the name of an instance method
|
10
36
|
#
|
11
37
|
# @note All this does is check whether the given string begins with a hash
|
@@ -21,6 +47,27 @@ module Taipo
|
|
21
47
|
str[0] == '#'
|
22
48
|
end
|
23
49
|
|
50
|
+
# Check if an object matches a given type definition
|
51
|
+
#
|
52
|
+
# @param object [Object] the object to check
|
53
|
+
# @param definition [String] the type definiton to check against
|
54
|
+
#
|
55
|
+
# @return [Boolean] the result
|
56
|
+
#
|
57
|
+
# @raise [::TypeError] if +definition+ is not a String
|
58
|
+
# @raise [Taipo::SyntaxError] if the type definitions in +checks+ are
|
59
|
+
# invalid
|
60
|
+
#
|
61
|
+
# @since 1.5.0
|
62
|
+
# @api private
|
63
|
+
def self.match?(object:, definition:)
|
64
|
+
msg = "The 'definition' argument must be of type String."
|
65
|
+
raise ::TypeError, msg unless definition.is_a? String
|
66
|
+
|
67
|
+
types = Taipo::Parser.parse definition
|
68
|
+
types.any? { |t| t.match? object }
|
69
|
+
end
|
70
|
+
|
24
71
|
# Return the type definition for an object
|
25
72
|
#
|
26
73
|
# @note This assume that each element returned by Enumerator#each has the same
|
@@ -77,5 +124,37 @@ module Taipo
|
|
77
124
|
def self.symbol?(str)
|
78
125
|
str[0] == ':'
|
79
126
|
end
|
127
|
+
|
128
|
+
# Throw an error with an appropriate message for a given object not matching
|
129
|
+
# a given type definition
|
130
|
+
#
|
131
|
+
# @param object [Object] the object that does not match +definition+
|
132
|
+
# @param name [String] the name of the object (with the code that originally
|
133
|
+
# called the #check method)
|
134
|
+
# @param definition [String] the type definition that does not match +object+
|
135
|
+
# @param result [Boolean] whether this is being called in respect of a
|
136
|
+
# return value (such as by {Taipo::Result::ClassMethods#result})
|
137
|
+
#
|
138
|
+
# @raise [Taipo::TypeError] the error
|
139
|
+
#
|
140
|
+
# @since 1.5.0
|
141
|
+
# @api private
|
142
|
+
def self.throw_error(object:, name:, definition:, result: false)
|
143
|
+
subject = (result) ? "The return value of #{name}" : "Object '#{name}'"
|
144
|
+
|
145
|
+
if Taipo::Utilities.instance_method? definition
|
146
|
+
msg = "#{subject} does not respond to #{definition}."
|
147
|
+
elsif Taipo::Utilities.symbol? definition
|
148
|
+
msg = "#{subject} is not equal to #{definition}."
|
149
|
+
elsif object.is_a? Enumerable
|
150
|
+
type_def = Taipo::Utilities.object_to_type_def object
|
151
|
+
msg = "#{subject} is #{type_def} but expected #{definition}."
|
152
|
+
else
|
153
|
+
class_name = object.class.name
|
154
|
+
msg = "#{subject} is #{class_name} but expected #{definition}."
|
155
|
+
end
|
156
|
+
|
157
|
+
raise Taipo::TypeError, msg
|
158
|
+
end
|
80
159
|
end
|
81
|
-
end
|
160
|
+
end
|
data/lib/taipo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: taipo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Camilleri
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -136,6 +136,7 @@ files:
|
|
136
136
|
- lib/taipo/parser/syntax_state.rb
|
137
137
|
- lib/taipo/parser/validater.rb
|
138
138
|
- lib/taipo/refinements.rb
|
139
|
+
- lib/taipo/result.rb
|
139
140
|
- lib/taipo/type_element.rb
|
140
141
|
- lib/taipo/type_element/children.rb
|
141
142
|
- lib/taipo/type_element/constraint.rb
|