yard_types 0.0.1 → 0.1.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.
- data/.travis.yml +2 -0
- data/Gemfile +1 -1
- data/README.md +5 -0
- data/lib/yard_types/types.rb +67 -8
- data/lib/yard_types/version.rb +1 -1
- data/lib/yard_types.rb +15 -1
- data/spec/errors_spec.rb +5 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/types_spec.rb +83 -0
- data/yard_type.gemspec +1 -0
- metadata +55 -19
- checksums.yaml +0 -7
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# yard_types
|
2
2
|
|
3
|
+
[](http://badge.fury.io/rb/yard_types)
|
3
4
|
[](https://travis-ci.org/pd/yard_types)
|
5
|
+
[](https://gemnasium.com/pd/yard_types)
|
6
|
+
[](https://codeclimate.com/github/pd/yard_types)
|
7
|
+
[](https://coveralls.io/r/pd/yard_types)
|
8
|
+
[](http://inch-ci.org/github/pd/yard_types)
|
4
9
|
|
5
10
|
Parse YARD type description strings -- eg `Array<#to_sym>` -- and use the
|
6
11
|
resulting types to check type correctness of objects at runtime.
|
data/lib/yard_types/types.rb
CHANGED
@@ -1,12 +1,25 @@
|
|
1
1
|
module YardTypes
|
2
2
|
|
3
|
+
# @api private
|
4
|
+
module OrList #:nodoc:
|
5
|
+
def or_list(ary)
|
6
|
+
size = ary.size
|
7
|
+
ary.to_enum.with_index.inject('') do |acc, (s, index)|
|
8
|
+
acc << s.to_s
|
9
|
+
acc << ", " if index < size - 2
|
10
|
+
acc << ", or " if index == size - 2
|
11
|
+
acc
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
3
16
|
# A +TypeConstraint+ specifies the set of acceptable types
|
4
17
|
# which can satisfy the constraint. Parsing any YARD type
|
5
18
|
# description will return a +TypeConstraint+ instance.
|
6
19
|
#
|
7
20
|
# @see YardTypes.parse
|
8
21
|
class TypeConstraint
|
9
|
-
# @return [Array<Type>]
|
22
|
+
# @return [Array<Type>] the list of types that will satisfy this constraint
|
10
23
|
attr_reader :accepted_types
|
11
24
|
|
12
25
|
# @param types [Array<Type>] the list of acceptable types
|
@@ -41,9 +54,9 @@ module YardTypes
|
|
41
54
|
end
|
42
55
|
end
|
43
56
|
|
44
|
-
# The base class for all supported types.
|
57
|
+
# @abstract The base class for all supported types.
|
45
58
|
class Type
|
46
|
-
# @return [String]
|
59
|
+
# @return [String] the YARD string naming this type
|
47
60
|
attr_accessor :name
|
48
61
|
|
49
62
|
# @todo This interface was just hacked into place while
|
@@ -70,6 +83,11 @@ module YardTypes
|
|
70
83
|
name
|
71
84
|
end
|
72
85
|
|
86
|
+
# @return [String] an English phrase describing this type.
|
87
|
+
def description
|
88
|
+
raise NotImplementedError
|
89
|
+
end
|
90
|
+
|
73
91
|
# @param obj [Object] Any object.
|
74
92
|
# @return [Boolean] whether the object is of this type.
|
75
93
|
# @raise [NotImplementedError] must be handled by the subclasses.
|
@@ -92,6 +110,11 @@ module YardTypes
|
|
92
110
|
@message = name[1..-1]
|
93
111
|
end
|
94
112
|
|
113
|
+
# (see Type#description)
|
114
|
+
def description
|
115
|
+
"an object that responds to #{name}"
|
116
|
+
end
|
117
|
+
|
95
118
|
# @param (see Type#check)
|
96
119
|
# @return [Boolean] +true+ if the object responds to +message+.
|
97
120
|
def check(obj)
|
@@ -117,14 +140,19 @@ module YardTypes
|
|
117
140
|
end
|
118
141
|
end
|
119
142
|
|
143
|
+
# (see Type#description)
|
144
|
+
def description
|
145
|
+
name
|
146
|
+
end
|
147
|
+
|
120
148
|
# @return [Module] the constant specified by +name+.
|
121
149
|
# @raise [TypeError] if the constant is neither a module nor a class
|
122
150
|
# @raise [NameError] if the specified constant could not be loaded.
|
123
151
|
def constant
|
124
152
|
@constant ||=
|
125
153
|
begin
|
126
|
-
const = name.split('::').reduce(Object) { |namespace,
|
127
|
-
namespace.const_get(
|
154
|
+
const = name.split('::').reduce(Object) { |namespace, inner_const|
|
155
|
+
namespace.const_get(inner_const)
|
128
156
|
}
|
129
157
|
|
130
158
|
unless const.kind_of?(Module)
|
@@ -151,6 +179,11 @@ module YardTypes
|
|
151
179
|
@literal_names ||= %w(true false nil void self)
|
152
180
|
end
|
153
181
|
|
182
|
+
# (see Type#description)
|
183
|
+
def description
|
184
|
+
name
|
185
|
+
end
|
186
|
+
|
154
187
|
# @param (see Type#check)
|
155
188
|
# @return [Boolean] +true+ if the object is exactly +true+, +false+, or
|
156
189
|
# +nil+ (depending on the value of +name+); for +void+ and +self+
|
@@ -175,6 +208,8 @@ module YardTypes
|
|
175
208
|
# @todo The current implementation of type checking here requires that the collection
|
176
209
|
# respond to +all?+; this may not be ideal.
|
177
210
|
class CollectionType < Type
|
211
|
+
include OrList
|
212
|
+
|
178
213
|
# @return [Array<Type>] the acceptable types for this collection's contents.
|
179
214
|
attr_accessor :types
|
180
215
|
|
@@ -185,11 +220,18 @@ module YardTypes
|
|
185
220
|
@types = types
|
186
221
|
end
|
187
222
|
|
188
|
-
#
|
223
|
+
# (see Type#to_s)
|
189
224
|
def to_s
|
190
225
|
"%s<%s>" % [name, types.map(&:to_s).join(', ')]
|
191
226
|
end
|
192
227
|
|
228
|
+
# (see Type#description)
|
229
|
+
def description
|
230
|
+
article = name[0] =~ /[aeiou]/i ? 'an' : 'a'
|
231
|
+
type_descriptions = types.map(&:description)
|
232
|
+
"#{article} #{name} of (#{or_list(type_descriptions)})"
|
233
|
+
end
|
234
|
+
|
193
235
|
# @param (see Type#check)
|
194
236
|
# @return [Boolean] +true+ if the object is both a kind of +name+, and all of
|
195
237
|
# its contents (if any) are of the types in +types+. Any combination, order,
|
@@ -216,11 +258,19 @@ module YardTypes
|
|
216
258
|
@types = types
|
217
259
|
end
|
218
260
|
|
219
|
-
#
|
261
|
+
# (see Type#to_s)
|
220
262
|
def to_s
|
221
263
|
"%s(%s)" % [name, types.map(&:to_s).join(', ')]
|
222
264
|
end
|
223
265
|
|
266
|
+
# (see Type#description)
|
267
|
+
def description
|
268
|
+
kind = name || 'tuple'
|
269
|
+
article = kind[0] =~ /[aeiou]/i ? 'an' : 'a'
|
270
|
+
contents = types.map(&:description).join(', ')
|
271
|
+
"#{article} #{kind} containing (#{contents})"
|
272
|
+
end
|
273
|
+
|
224
274
|
# @param (see Type#check)
|
225
275
|
# @return [Boolean] +true+ if the collection's +length+ is exactly the length of
|
226
276
|
# the expected +types+, and each element with the collection is of the type
|
@@ -253,6 +303,8 @@ module YardTypes
|
|
253
303
|
# @todo Enforce kind, eg +HashWithIndifferentAccess{#to_sym => Array}+,
|
254
304
|
# in case you _really_ care that it's indifferent. Maybe?
|
255
305
|
class HashType < Type
|
306
|
+
include OrList
|
307
|
+
|
256
308
|
# @return [Array<Type>] the set of acceptable types for keys
|
257
309
|
attr_reader :key_types
|
258
310
|
|
@@ -279,6 +331,14 @@ module YardTypes
|
|
279
331
|
]
|
280
332
|
end
|
281
333
|
|
334
|
+
# (see Type#description)
|
335
|
+
def description
|
336
|
+
article = name[0] =~ /[aeiou]/i ? 'an' : 'a'
|
337
|
+
key_descriptions = or_list(key_types.map(&:description))
|
338
|
+
value_descriptions = or_list(value_types.map(&:description))
|
339
|
+
"#{article} #{name} with keys of (#{key_descriptions}) and values of (#{value_descriptions})"
|
340
|
+
end
|
341
|
+
|
282
342
|
# @param (see Type#check)
|
283
343
|
# @return [Boolean] +true+ if the object responds to both +keys+ and +values+,
|
284
344
|
# and every key type checks against a type in +key_types+, and every value
|
@@ -289,5 +349,4 @@ module YardTypes
|
|
289
349
|
obj.values.all? { |value| value_types.any? { |t| t.check(value) } }
|
290
350
|
end
|
291
351
|
end
|
292
|
-
|
293
352
|
end
|
data/lib/yard_types/version.rb
CHANGED
data/lib/yard_types.rb
CHANGED
@@ -2,9 +2,12 @@ require "yard_types/version"
|
|
2
2
|
require "yard_types/types"
|
3
3
|
require "yard_types/parser"
|
4
4
|
|
5
|
+
# {YardTypes} provides a parser for YARD type descriptions, and
|
6
|
+
# testing whether objects are of the specified types.
|
5
7
|
module YardTypes
|
6
8
|
extend self
|
7
9
|
|
10
|
+
# @abstract Base class for {Success} and {Failure}
|
8
11
|
class Result
|
9
12
|
def initialize(pass = false)
|
10
13
|
@pass = pass
|
@@ -15,12 +18,18 @@ module YardTypes
|
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
21
|
+
# Returned from {YardTypes.validate} when a type check succeeds,
|
22
|
+
# providing the particular type which satisfied the
|
23
|
+
# {TypeConstraint}.
|
18
24
|
class Success < Result
|
19
25
|
def initialize
|
20
26
|
super(true)
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
30
|
+
# Returned from {YardTypes.validate} when a type check fails,
|
31
|
+
# providing a reference to the {TypeConstraint} and a means of
|
32
|
+
# generating error messages describing the error.
|
24
33
|
class Failure < Result
|
25
34
|
def initialize
|
26
35
|
super(false)
|
@@ -42,7 +51,12 @@ module YardTypes
|
|
42
51
|
Parser.parse(type)
|
43
52
|
end
|
44
53
|
|
45
|
-
#
|
54
|
+
# Parses a type identifier with {#parse}, then validates that the
|
55
|
+
# given +obj+ satisfies the type constraint.
|
56
|
+
#
|
57
|
+
# @param type [String, Array<String>] A YARD type description; see {#parse}.
|
58
|
+
# @param obj [Object] Any object.
|
59
|
+
# @return [Result] success or failure.
|
46
60
|
# @todo deprecate; rename it +check+ to match everything else.
|
47
61
|
def validate(type, obj)
|
48
62
|
constraint = parse(type)
|
data/spec/errors_spec.rb
CHANGED
@@ -6,6 +6,11 @@ describe 'Defensive error raising' do
|
|
6
6
|
expect { type.check(nil) }.to raise_error(NotImplementedError)
|
7
7
|
end
|
8
8
|
|
9
|
+
specify 'Type#description raises NotImplementedError' do
|
10
|
+
type = YardTypes::Type.new('Foo')
|
11
|
+
expect { type.description }.to raise_error(NotImplementedError)
|
12
|
+
end
|
13
|
+
|
9
14
|
specify 'LiteralType raises when checking for an unsupported literal' do
|
10
15
|
type = YardTypes::LiteralType.new('zero')
|
11
16
|
expect { type.check(0) }.to raise_error(NotImplementedError, /zero/)
|
data/spec/spec_helper.rb
CHANGED
data/spec/types_spec.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module YardTypes
|
4
|
+
describe DuckType do
|
5
|
+
context '#description' do
|
6
|
+
it "says the object should respond to its message" do
|
7
|
+
type = DuckType.new('#msg')
|
8
|
+
expect(type.description).to eq('an object that responds to #msg')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe KindType do
|
14
|
+
context '#description' do
|
15
|
+
it "says the object should be a kind of its module" do
|
16
|
+
type = KindType.new('Struct')
|
17
|
+
expect(type.description).to eq('Struct')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe LiteralType do
|
23
|
+
context '#description' do
|
24
|
+
LiteralType.names.each do |literal|
|
25
|
+
specify literal do
|
26
|
+
type = LiteralType.new(literal)
|
27
|
+
expect(type.description).to eq(literal)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe CollectionType do
|
34
|
+
context '#description' do
|
35
|
+
let(:type) { CollectionType.new('Array', [KindType.new('Foo'), DuckType.new('#bar')]) }
|
36
|
+
|
37
|
+
it "specifies its Kind" do
|
38
|
+
expect(type.description).to match(/Array/)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "specifies its inner content types" do
|
42
|
+
expect(type.description).to match(/Foo, or an object that responds to #bar/)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe TupleType do
|
48
|
+
context '#description' do
|
49
|
+
let(:type) { TupleType.new('Array', [LiteralType.new('false'), KindType.new('Set')]) }
|
50
|
+
|
51
|
+
it 'specifies its Kind' do
|
52
|
+
expect(type.description).to match(/Array/)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'specifies its contents, in order' do
|
56
|
+
expect(type.description).to match(/\(false, Set\)/)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'omits its Kind if unspecified' do
|
60
|
+
type = YardTypes.parse('(Set, Numeric)').accepted_types.first
|
61
|
+
expect(type.description).to eql('a tuple containing (Set, Numeric)')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe HashType do
|
67
|
+
context '#description' do
|
68
|
+
let(:type) { HashType.new('Hash', [DuckType.new('#to_s')], [KindType.new('Date'), KindType.new('DateTime')]) }
|
69
|
+
|
70
|
+
it "specifies its Kind" do
|
71
|
+
expect(type.description).to match(/Hash/)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "specifies its key types" do
|
75
|
+
expect(type.description).to match(/keys of \(an object that responds to #to_s\)/)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "specifies its value types" do
|
79
|
+
expect(type.description).to match(/values of \(Date, or DateTime\)/)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/yard_type.gemspec
CHANGED
metadata
CHANGED
@@ -1,83 +1,110 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yard_types
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Kyle Hargraves
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-
|
12
|
+
date: 2014-11-20 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: bundler
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
|
-
- -
|
19
|
+
- - ~>
|
18
20
|
- !ruby/object:Gem::Version
|
19
21
|
version: '1.5'
|
20
22
|
type: :development
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
|
-
- -
|
27
|
+
- - ~>
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '1.5'
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: rake
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
|
-
- -
|
35
|
+
- - ~>
|
32
36
|
- !ruby/object:Gem::Version
|
33
37
|
version: '10.0'
|
34
38
|
type: :development
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
|
-
- -
|
43
|
+
- - ~>
|
39
44
|
- !ruby/object:Gem::Version
|
40
45
|
version: '10.0'
|
41
46
|
- !ruby/object:Gem::Dependency
|
42
47
|
name: rspec
|
43
48
|
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
44
50
|
requirements:
|
45
|
-
- -
|
51
|
+
- - ~>
|
46
52
|
- !ruby/object:Gem::Version
|
47
53
|
version: '3.0'
|
48
54
|
type: :development
|
49
55
|
prerelease: false
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
51
58
|
requirements:
|
52
|
-
- -
|
59
|
+
- - ~>
|
53
60
|
- !ruby/object:Gem::Version
|
54
61
|
version: '3.0'
|
55
62
|
- !ruby/object:Gem::Dependency
|
56
63
|
name: simplecov
|
57
64
|
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
58
66
|
requirements:
|
59
|
-
- -
|
67
|
+
- - ~>
|
60
68
|
- !ruby/object:Gem::Version
|
61
69
|
version: 0.7.1
|
62
70
|
type: :development
|
63
71
|
prerelease: false
|
64
72
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
65
74
|
requirements:
|
66
|
-
- -
|
75
|
+
- - ~>
|
67
76
|
- !ruby/object:Gem::Version
|
68
77
|
version: 0.7.1
|
69
78
|
- !ruby/object:Gem::Dependency
|
70
79
|
name: pry
|
71
80
|
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
72
82
|
requirements:
|
73
|
-
- -
|
83
|
+
- - ! '>'
|
74
84
|
- !ruby/object:Gem::Version
|
75
85
|
version: '0'
|
76
86
|
type: :development
|
77
87
|
prerelease: false
|
78
88
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
79
90
|
requirements:
|
80
|
-
- -
|
91
|
+
- - ! '>'
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: coveralls
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>'
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>'
|
81
108
|
- !ruby/object:Gem::Version
|
82
109
|
version: '0'
|
83
110
|
description: Your API docs say you return Array<#to_date>, but do you really?
|
@@ -87,8 +114,8 @@ executables: []
|
|
87
114
|
extensions: []
|
88
115
|
extra_rdoc_files: []
|
89
116
|
files:
|
90
|
-
-
|
91
|
-
-
|
117
|
+
- .gitignore
|
118
|
+
- .travis.yml
|
92
119
|
- Gemfile
|
93
120
|
- LICENSE.txt
|
94
121
|
- README.md
|
@@ -101,33 +128,42 @@ files:
|
|
101
128
|
- spec/parsing_spec.rb
|
102
129
|
- spec/spec_helper.rb
|
103
130
|
- spec/type_checking_spec.rb
|
131
|
+
- spec/types_spec.rb
|
104
132
|
- yard_type.gemspec
|
105
133
|
homepage: https://github.com/pd/yard_types
|
106
134
|
licenses:
|
107
135
|
- MIT
|
108
|
-
metadata: {}
|
109
136
|
post_install_message:
|
110
137
|
rdoc_options: []
|
111
138
|
require_paths:
|
112
139
|
- lib
|
113
140
|
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
114
142
|
requirements:
|
115
|
-
- -
|
143
|
+
- - ! '>='
|
116
144
|
- !ruby/object:Gem::Version
|
117
145
|
version: '0'
|
146
|
+
segments:
|
147
|
+
- 0
|
148
|
+
hash: 1195132354547834267
|
118
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
119
151
|
requirements:
|
120
|
-
- -
|
152
|
+
- - ! '>='
|
121
153
|
- !ruby/object:Gem::Version
|
122
154
|
version: '0'
|
155
|
+
segments:
|
156
|
+
- 0
|
157
|
+
hash: 1195132354547834267
|
123
158
|
requirements: []
|
124
159
|
rubyforge_project:
|
125
|
-
rubygems_version:
|
160
|
+
rubygems_version: 1.8.23.2
|
126
161
|
signing_key:
|
127
|
-
specification_version:
|
162
|
+
specification_version: 3
|
128
163
|
summary: Parse and validate objects against YARD type descriptions.
|
129
164
|
test_files:
|
130
165
|
- spec/errors_spec.rb
|
131
166
|
- spec/parsing_spec.rb
|
132
167
|
- spec/spec_helper.rb
|
133
168
|
- spec/type_checking_spec.rb
|
169
|
+
- spec/types_spec.rb
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: 722cc485964dc469732d1ef265f650bae6c029ee
|
4
|
-
data.tar.gz: 4e85d42f94b0d31a7991e076f62acdcc717c1c27
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: 154fb6a437ecd17d7c331836224e6706e4edb411f4de4dd3723fc98131f20b9487b5bfb4c6d33ad0bbd53c9f23577fdbc3bd44af58cea4c35c491b96737dc387
|
7
|
-
data.tar.gz: 11800a82a3a26ddc32c4472f086fa4ba20f53d9b98f8ebb03e2098d495b2cbef1c504142d4976c48d6f92b4c8731ba6095178ccb85cacbe8a899ff78acb0f173
|