taipo 1.4.0 → 1.5.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.
- 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
|