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 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