yard_types 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,4 +1,6 @@
1
1
  language: ruby
2
+ install:
3
+ - bundle install --retry=3
2
4
  rvm:
3
5
  - 1.9.3
4
6
  - 2.0.0
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in yard_type_check.gemspec
3
+ # Specify your gem's dependencies in yard_types.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # yard_types
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/yard_types.svg)](http://badge.fury.io/rb/yard_types)
3
4
  [![Build Status](https://travis-ci.org/pd/yard_types.svg?branch=master)](https://travis-ci.org/pd/yard_types)
5
+ [![Dependency Status](https://gemnasium.com/pd/yard_types.svg)](https://gemnasium.com/pd/yard_types)
6
+ [![Code Climate](https://codeclimate.com/github/pd/yard_types.png)](https://codeclimate.com/github/pd/yard_types)
7
+ [![Coverage Status](https://coveralls.io/repos/pd/yard_types/badge.png)](https://coveralls.io/r/pd/yard_types)
8
+ [![Inline docs](http://inch-ci.org/github/pd/yard_types.svg?branch=master)](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.
@@ -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, const|
127
- namespace.const_get(const)
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
- # @return (see Type#to_s)
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
- # @return (see Type#to_s)
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
@@ -1,3 +1,3 @@
1
1
  module YardTypes
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.1"
3
3
  end
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
- # @return [Result]
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
@@ -1,4 +1,11 @@
1
1
  require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ Coveralls::SimpleCov::Formatter,
6
+ SimpleCov::Formatter::HTMLFormatter,
7
+ ]
8
+
2
9
  SimpleCov.start do
3
10
  add_filter "/spec/"
4
11
  end
@@ -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
@@ -23,4 +23,5 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "rspec", "~> 3.0"
24
24
  spec.add_development_dependency "simplecov", "~> 0.7.1"
25
25
  spec.add_development_dependency "pry", "> 0"
26
+ spec.add_development_dependency "coveralls", "> 0"
26
27
  end
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.0.1
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-07-16 00:00:00.000000000 Z
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
- - ".gitignore"
91
- - ".travis.yml"
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: 2.2.0
160
+ rubygems_version: 1.8.23.2
126
161
  signing_key:
127
- specification_version: 4
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