taipo 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.simplecov +3 -0
- data/.travis.yml +14 -0
- data/LICENSE.md +1 -1
- data/README.md +8 -0
- data/lib/taipo/check.rb +42 -8
- data/lib/taipo/parser/validater.rb +12 -1
- data/lib/taipo/type_element/child_type.rb +15 -1
- data/lib/taipo/type_element.rb +19 -0
- data/lib/taipo/version.rb +1 -1
- data/lib/taipo.rb +98 -16
- data/taipo.gemspec +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47a30b8ff6c187495ba01a26d23e30af086686d9
|
4
|
+
data.tar.gz: b8b8473c03c6f3bcebe33ea60e9c41e59359e45d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6523770dc0da188909c896d2f9f005af8aed1aa7f11e75a48c8a3eee8d80838e7e88a333ea0cff45e2a18fad3a5f0e2b9d15463222a47ffff74e35cb2dbb6fde
|
7
|
+
data.tar.gz: 1abfe9da9fcf22d927afe9b8b7c14e7a3fd0b4ecca5c004a071d39c57d501640509496696a19d1d77c84328351d646ef17bff10660e768bed611fb2ca61a155a
|
data/.simplecov
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
env:
|
2
|
+
global:
|
3
|
+
- CC_TEST_REPORTER_ID=787a2f89b15c637323c7340d65ec17e898ac44480706b4b4122ea040c2a88f1d
|
4
|
+
language: ruby
|
5
|
+
rvm:
|
6
|
+
- 2.4.1
|
7
|
+
before_script:
|
8
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
9
|
+
- chmod +x ./cc-test-reporter
|
10
|
+
- ./cc-test-reporter before-build
|
11
|
+
script:
|
12
|
+
- bundle exec rake test
|
13
|
+
after_script:
|
14
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
data/LICENSE.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
This is free and unencumbered software released into the public domain.
|
2
2
|
|
3
|
-
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
4
4
|
|
5
5
|
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
6
6
|
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
[![Gem Version](https://badge.fury.io/rb/taipo.svg)](https://badge.fury.io/rb/taipo) [![Inline docs](http://inch-ci.org/github/pyrmont/taipo.svg?branch=master)](http://inch-ci.org/github/pyrmont/taipo)
|
2
|
+
[![Build Status](https://travis-ci.org/pyrmont/taipo.svg?branch=master)](https://travis-ci.org/pyrmont/taipo)
|
3
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/7b5dcb371ee422b27f0c/test_coverage)](https://codeclimate.com/github/pyrmont/taipo/test_coverage)
|
2
4
|
|
3
5
|
# Taipo
|
4
6
|
|
@@ -138,6 +140,12 @@ Found a bug? I’d love to know about it. The best way is to report them in the
|
|
138
140
|
|
139
141
|
If you’re interested in contributing to Taipo, feel free to fork and submit a pull request.
|
140
142
|
|
143
|
+
## Versioning
|
144
|
+
|
145
|
+
Taipo uses [Semantic Versioning 2.0.0][sv2].
|
146
|
+
|
147
|
+
[sv2]: http://semver.org/
|
148
|
+
|
141
149
|
## Colophon
|
142
150
|
|
143
151
|
Taipo began as, and remains primarily, an exercise to improve my programming skills. If Taipo has piqued your interest in adding type checks to Ruby, consider some of the other options, such as [Contracts][cnt], [Rtype][rty], [Rubype][rub] or [Sig][sig].
|
data/lib/taipo/check.rb
CHANGED
@@ -13,8 +13,13 @@ module Taipo
|
|
13
13
|
# Syntactic sugar to allow a user to write +check types,...+ and +review
|
14
14
|
# types,...+
|
15
15
|
#
|
16
|
-
#
|
17
|
-
|
16
|
+
# The alias is written with underscores in order to support making aliasing
|
17
|
+
# with the keyword +types+ optional (since it's possible the user wishes to
|
18
|
+
# use this keyword for other purposes).
|
19
|
+
#
|
20
|
+
# @since 1.1.0
|
21
|
+
# @api private
|
22
|
+
alias __types__ binding
|
18
23
|
|
19
24
|
# Check whether the given arguments match the given type definition in the
|
20
25
|
# given context
|
@@ -39,7 +44,7 @@ module Taipo
|
|
39
44
|
# @since 1.0.0
|
40
45
|
#
|
41
46
|
# @example
|
42
|
-
# require 'taipo
|
47
|
+
# require 'taipo'
|
43
48
|
#
|
44
49
|
# class A
|
45
50
|
# include Taipo::Check
|
@@ -57,10 +62,10 @@ module Taipo
|
|
57
62
|
#
|
58
63
|
# a = A.new()
|
59
64
|
# a.foo('Hello world!') #=> "Hello world!"
|
60
|
-
# a.bar('Goodbye world!') #=>
|
65
|
+
# a.bar('Goodbye world!') #=> Taipo::TypeError
|
61
66
|
def check(context, collect_invalids = false, **checks)
|
62
67
|
msg = "The first argument to this method must be of type Binding."
|
63
|
-
raise TypeError, msg unless context.is_a? Binding
|
68
|
+
raise ::TypeError, msg unless context.is_a? Binding
|
64
69
|
|
65
70
|
checks.reduce(Array.new) do |memo,(k,v)|
|
66
71
|
arg = if k[0] == '@' && self.instance_variable_defined?(k)
|
@@ -84,8 +89,8 @@ module Taipo
|
|
84
89
|
if Taipo::instance_method? v
|
85
90
|
msg = "Object '#{k}' does not respond to #{v}."
|
86
91
|
elsif arg.is_a? Enumerable
|
87
|
-
|
88
|
-
msg = "Object '#{k}' is #{
|
92
|
+
type_def = Taipo.object_to_type_def arg
|
93
|
+
msg = "Object '#{k}' is #{type_def} but expected #{v}."
|
89
94
|
else
|
90
95
|
msg = "Object '#{k}' is #{arg.class.name} but expected #{v}."
|
91
96
|
end
|
@@ -113,5 +118,34 @@ module Taipo
|
|
113
118
|
def review(context, **checks)
|
114
119
|
self.check(context, true, checks)
|
115
120
|
end
|
121
|
+
|
122
|
+
# Perform operations if this module is extended
|
123
|
+
#
|
124
|
+
# This is the callback called by Ruby when a module is included. In this
|
125
|
+
# case, the callback will alias the method +__types__+ as +types+ if
|
126
|
+
# {Taipo.alias?} returns true.
|
127
|
+
#
|
128
|
+
# @param extender [Class|Module] the class or module extending this module
|
129
|
+
#
|
130
|
+
# @since 1.1.0
|
131
|
+
# @api private
|
132
|
+
def self.extended(extender)
|
133
|
+
extender.singleton_class.send(:alias_method, :types, :__types__) if
|
134
|
+
Taipo.alias?
|
135
|
+
end
|
136
|
+
|
137
|
+
# Perform operations if this module is included
|
138
|
+
#
|
139
|
+
# This is the callback called by Ruby when a module is included. In this
|
140
|
+
# case, the callback will alias the method +__types__+ as +types+ if
|
141
|
+
# {Taipo.alias?} returns true.
|
142
|
+
#
|
143
|
+
# @param includer [Class|Module] the class or module including this module
|
144
|
+
#
|
145
|
+
# @since 1.1.0
|
146
|
+
# @api private
|
147
|
+
def self.included(includer)
|
148
|
+
includer.send(:alias_method, :types, :__types__) if Taipo.alias?
|
149
|
+
end
|
116
150
|
end
|
117
|
-
end
|
151
|
+
end
|
@@ -23,7 +23,18 @@ module Taipo
|
|
23
23
|
# One special case is where the name is left blank. The validater will
|
24
24
|
# accept this as valid. {Taipo::Parser} will implictly add the name
|
25
25
|
# 'Object' when parsing the type definition. This allows a clean syntax for
|
26
|
-
# duck types (
|
26
|
+
# duck types (discussed in further detail below).
|
27
|
+
#
|
28
|
+
# ==== Duck Types
|
29
|
+
#
|
30
|
+
# '#to_s', '(#foo, #bar)'
|
31
|
+
#
|
32
|
+
# As noted above, duck types can be specified by using a blank name. Duck
|
33
|
+
# types are really constraints (discussed in further detail below) on the
|
34
|
+
# class Object. While normally constraints need to be enclosed in
|
35
|
+
# parentheses, if there is a blank name and only one method constraint, the
|
36
|
+
# parentheses can be omitted. For defining duck types that respond to
|
37
|
+
# multiple methods, the parentheses are required.
|
27
38
|
#
|
28
39
|
# === Collections
|
29
40
|
#
|
@@ -11,10 +11,24 @@ module Taipo
|
|
11
11
|
# @param components [Array<Taipo::TypeElement>] the components that will make up the ChildType
|
12
12
|
#
|
13
13
|
# @since 1.0.0
|
14
|
-
# @api private
|
14
|
+
# @api private
|
15
15
|
def initialize(components = nil)
|
16
16
|
components.each { |c| self.push c } unless components.nil?
|
17
17
|
end
|
18
|
+
|
19
|
+
# Return the String representation of this ChildType
|
20
|
+
#
|
21
|
+
# @since 1.1.0
|
22
|
+
# @api private
|
23
|
+
def to_s
|
24
|
+
inner = self.reduce(nil) do |memo_e,component|
|
25
|
+
el = component.reduce(nil) do |memo_c,c|
|
26
|
+
(memo_c.nil?) ? c.to_s : memo_c + '|' + c.to_s
|
27
|
+
end
|
28
|
+
(memo_e.nil?) ? el : memo_e + ',' + el
|
29
|
+
end
|
30
|
+
'<' + inner + '>'
|
31
|
+
end
|
18
32
|
end
|
19
33
|
end
|
20
34
|
end
|
data/lib/taipo/type_element.rb
CHANGED
@@ -196,5 +196,24 @@ module Taipo
|
|
196
196
|
c.constrain?(arg)
|
197
197
|
end
|
198
198
|
end
|
199
|
+
|
200
|
+
# Return the String representation of this TypeElement
|
201
|
+
#
|
202
|
+
# @since 1.1.0
|
203
|
+
# @api private
|
204
|
+
def to_s
|
205
|
+
name_str = @name
|
206
|
+
child_type_str = (@child_type.nil?) ? '' : @child_type.to_s
|
207
|
+
constraints_str = if @constraints.nil?
|
208
|
+
''
|
209
|
+
else
|
210
|
+
inner = @constraints.reduce('') do |memo,c|
|
211
|
+
(memo == '') ? c.to_s : memo + ',' + c.to_s
|
212
|
+
end
|
213
|
+
'(' + inner + ')'
|
214
|
+
end
|
215
|
+
name_str + child_type_str + constraints_str
|
216
|
+
end
|
217
|
+
|
199
218
|
end
|
200
219
|
end
|
data/lib/taipo/version.rb
CHANGED
data/lib/taipo.rb
CHANGED
@@ -11,19 +11,58 @@ require 'taipo/parser'
|
|
11
11
|
# By including the module {Taipo::Check}, a user can call the
|
12
12
|
# {Taipo::Check#check} or {Taipo::Check#review} methods in their classes
|
13
13
|
# whenever a guard statement is necessary. Expectations are written as type
|
14
|
-
# definitions
|
15
|
-
#
|
16
|
-
#
|
14
|
+
# definitions. A type definition contains the name of the type and the type
|
15
|
+
# definitions of any elements it contains (if it is enumerable). Optional
|
16
|
+
# constraints may be specified and sum types are also possible. See
|
17
|
+
# {Taipo::Parser::Validater} for the full syntax.
|
17
18
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
19
|
+
# Taipo works by:
|
20
|
+
# 1. extracting the values of the arguments to be checked from a Binding;
|
21
|
+
# 2. transforming the type definitions provided as Strings into an array of
|
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.
|
25
|
+
#
|
26
|
+
# As syntactic sugar, the {Taipo::Check} module will by default alias
|
27
|
+
# +Kernel#binding+ with the keyword +types+. This allows the user to call
|
28
|
+
# {Taipo::Check#check} by writing +check types+ (with a similar syntax for
|
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}.
|
22
31
|
#
|
23
32
|
# @since 1.0.0
|
24
33
|
# @see https://github.com/pyrmont/taipo
|
25
34
|
module Taipo
|
26
35
|
|
36
|
+
# The setting for whether +Kernel#binding+ should be aliased with the keyword
|
37
|
+
# +types+.
|
38
|
+
#
|
39
|
+
# @since 1.1.0
|
40
|
+
# @api private
|
41
|
+
@@alias = true
|
42
|
+
|
43
|
+
# Set whether +Kernel#binding+ should be aliased with the keyword +types+.
|
44
|
+
#
|
45
|
+
# @param v [Boolean] Whether to alias
|
46
|
+
#
|
47
|
+
# @since 1.1.0
|
48
|
+
# @api private
|
49
|
+
def self.alias=(v)
|
50
|
+
msg = "The argument to this method must be a Boolean."
|
51
|
+
raise ::TypeError, msg unless v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
52
|
+
|
53
|
+
@@alias = v
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check whether +Kernel#binding+ should be aliased with the keyword +types+.
|
57
|
+
#
|
58
|
+
# @return [Boolean] the result
|
59
|
+
#
|
60
|
+
# @since 1.1.0
|
61
|
+
# @api private
|
62
|
+
def self.alias?
|
63
|
+
@@alias
|
64
|
+
end
|
65
|
+
|
27
66
|
# Check if a string is the name of an instance method
|
28
67
|
#
|
29
68
|
# @note All this does is check whether the given string begins with a hash
|
@@ -39,17 +78,60 @@ module Taipo
|
|
39
78
|
str[0] == '#'
|
40
79
|
end
|
41
80
|
|
42
|
-
#
|
81
|
+
# Return the type definition for an object
|
43
82
|
#
|
44
|
-
# @
|
83
|
+
# @note This assume that each element returned by Enumerator#each has the same
|
84
|
+
# number of components.
|
45
85
|
#
|
46
|
-
# @
|
86
|
+
# @param arg [Object] the object
|
47
87
|
#
|
48
|
-
# @
|
88
|
+
# @return [String] a type definition of the object
|
89
|
+
#
|
90
|
+
# @since 1.1.0
|
91
|
+
# @api private
|
92
|
+
def self.object_to_type_def(obj)
|
93
|
+
return obj.class.name unless obj.is_a? Enumerable
|
94
|
+
|
95
|
+
if obj.is_a? Array
|
96
|
+
element_types = Hash.new
|
97
|
+
obj.each { |o| element_types[self.object_to_type_def(o)] = true }
|
98
|
+
if element_types.empty?
|
99
|
+
obj.class.name
|
100
|
+
else
|
101
|
+
obj.class.name + '<' + element_types.keys.join('|') + '>'
|
102
|
+
end
|
103
|
+
else
|
104
|
+
element_types = Array.new
|
105
|
+
obj.each.with_index do |element,index_e|
|
106
|
+
element.each.with_index do |component,index_c|
|
107
|
+
element_types[index_c] = Hash.new if index_e == 0
|
108
|
+
c_type = self.object_to_type_def(component)
|
109
|
+
element_types[index_c][c_type] = true
|
110
|
+
end
|
111
|
+
end
|
112
|
+
inner = element_types.reduce('') do |memo,e|
|
113
|
+
e_type = e.keys.join('|')
|
114
|
+
(memo == '') ? e_type : memo + ',' + e_type
|
115
|
+
end
|
116
|
+
if element_types.empty?
|
117
|
+
obj.class.name
|
118
|
+
else
|
119
|
+
obj.class.name + '<' + inner + '>'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return a String representation of an array of {Taipo::TypeElement}
|
125
|
+
#
|
126
|
+
# @param arg [Array<Taipo::TypeElement>] the array of {Taipo::TypeElement}
|
127
|
+
#
|
128
|
+
# @return [String] the String representation
|
129
|
+
#
|
130
|
+
# @since 1.1.0
|
49
131
|
# @api private
|
50
|
-
def self.
|
51
|
-
|
52
|
-
|
53
|
-
|
132
|
+
def self.types_to_s(types)
|
133
|
+
types.reduce('') do |memo,t|
|
134
|
+
(memo == '') ? t.to_s : memo + '|' + t.to_s
|
135
|
+
end
|
54
136
|
end
|
55
|
-
end
|
137
|
+
end
|
data/taipo.gemspec
CHANGED
@@ -25,5 +25,6 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_development_dependency "minitest", "~> 5.10.3"
|
26
26
|
spec.add_development_dependency "minitest-reporters", "~> 1.1.19"
|
27
27
|
spec.add_development_dependency "shoulda-context", "~> 1.2.0"
|
28
|
+
spec.add_development_dependency "simplecov", "~> 0.15.1"
|
28
29
|
spec.add_development_dependency "yard", "~> 0.9.12"
|
29
30
|
end
|
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.0
|
4
|
+
version: 1.1.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-01-
|
11
|
+
date: 2018-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 1.2.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.15.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.15.1
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: yard
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,6 +118,8 @@ extensions: []
|
|
104
118
|
extra_rdoc_files: []
|
105
119
|
files:
|
106
120
|
- ".gitignore"
|
121
|
+
- ".simplecov"
|
122
|
+
- ".travis.yml"
|
107
123
|
- Gemfile
|
108
124
|
- LICENSE.md
|
109
125
|
- README.md
|