typedeaf 0.0.1 → 0.1.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 +8 -0
- data/Gemfile +3 -1
- data/README.md +76 -17
- data/benchmarks/bench_helper.rb +6 -0
- data/benchmarks/missing_arguments.rb +27 -0
- data/benchmarks/missing_arguments.txt +15 -0
- data/benchmarks/parameter_calls.rb +26 -0
- data/benchmarks/parameter_calls.txt +15 -0
- data/benchmarks/simple_calls.rb +24 -0
- data/benchmarks/simple_calls.txt +16 -0
- data/lib/typedeaf.rb +2 -63
- data/lib/typedeaf/arguments.rb +12 -0
- data/lib/typedeaf/classmethods.rb +88 -0
- data/lib/typedeaf/instancemethods.rb +145 -0
- data/lib/typedeaf/version.rb +1 -1
- data/spec/spec_helper.rb +8 -1
- data/spec/typedeaf_spec.rb +138 -4
- data/typedeaf.gemspec +1 -2
- metadata +18 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9fea842c38105ee58ed6274182e98d48af63414
|
4
|
+
data.tar.gz: 1174982311c6dc067f43186110909fc3b8f9b2be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cdb9589f43469a270fdea4efea6066ce48c4b95376abd16530b6e87851d7647da16bf6df452c8a24ade90186899c832be56a49035c99845cf5c96f1ae502ac5
|
7
|
+
data.tar.gz: ad794a1c79c07883854250e988f12f6b5901cc364ca85151fb3934fab0bac496d131cc61a15501e1f9453bb4bcb20250f31af0c6edc8a0db7d62348fb649cec0
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,35 +1,94 @@
|
|
1
1
|
# Typedeaf
|
2
2
|
|
3
|
+
[](https://travis-ci.org/rtyler/typedeaf)
|
4
|
+
|
3
5
|
Typedeaf is a gem to help add some type-checking to method declarations in Ruby
|
4
6
|
|
5
7
|
|
8
|
+
[RDoc](http://www.rubydoc.info/github/rtyler/typedeaf/master/frames)
|
9
|
+
|
10
|
+
|
6
11
|
## Usage
|
7
12
|
|
13
|
+
### Writing your Typedeaf'd code
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'typedeaf'
|
17
|
+
|
18
|
+
class Logger
|
19
|
+
include Typedeaf
|
20
|
+
|
21
|
+
# Log an error
|
22
|
+
#
|
23
|
+
# @param [String] messaage
|
24
|
+
define :error, message: String do
|
25
|
+
# Just delegate to the #log method nad pass
|
26
|
+
# the level of error
|
27
|
+
self.log(message, :error)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Log a simple message to STDOUT
|
31
|
+
#
|
32
|
+
# @param [String] message The log message to log
|
33
|
+
# @param [Symbol] level The level at which to log the
|
34
|
+
# message, defaults to :info
|
35
|
+
define :log, message: String, level: default(:info, Symbol) do
|
36
|
+
puts "[#{level}] #{message}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
### Calling Typedeaf'd code
|
42
|
+
|
8
43
|
```
|
9
|
-
[1] pry(main)> require '
|
44
|
+
[1] pry(main)> require './logger'
|
10
45
|
=> true
|
11
|
-
[2] pry(main)>
|
12
|
-
[2] pry(main)* include Typedeaf
|
13
|
-
[2] pry(main)*
|
14
|
-
[2] pry(main)* define :log, message: String, level: Symbol do
|
15
|
-
[2] pry(main)* puts "Logging #{message} at level #{level}"
|
16
|
-
[2] pry(main)* end
|
17
|
-
[2] pry(main)* end
|
18
|
-
=> Logger
|
19
|
-
[3] pry(main)> l = Logger.new
|
46
|
+
[2] pry(main)> l = Logger.new
|
20
47
|
=> #<Logger:0x00000803c616b8>
|
21
|
-
[
|
22
|
-
|
48
|
+
[3] pry(main)> l.log 'test 1, 2, 3'
|
49
|
+
[info] test 1, 2, 3
|
50
|
+
=> nil
|
51
|
+
[4] pry(main)> l.log 'this is SUPER SERIOUS', :critical
|
52
|
+
[critical] this is SUPER SERIOUS
|
23
53
|
=> nil
|
24
|
-
[5] pry(main)> l.
|
54
|
+
[5] pry(main)> l.error(5) # wrong type!
|
25
55
|
Typedeaf::InvalidTypeException: Expected `message` to be a kind of String but was Fixnum
|
26
|
-
from /usr/home/tyler/source/github/ruby/typedeaf/lib/typedeaf.rb:
|
27
|
-
[6] pry(main)> l.log('
|
28
|
-
Typedeaf::InvalidTypeException: Expected `level` to be a kind of Symbol but was
|
29
|
-
from /usr/home/tyler/source/github/ruby/typedeaf/lib/typedeaf.rb:
|
56
|
+
from /usr/home/tyler/source/github/ruby/typedeaf/lib/typedeaf.rb:58:in `type_validation!'
|
57
|
+
[6] pry(main)> l.log("This doesn't use the right type either", 1)
|
58
|
+
Typedeaf::InvalidTypeException: Expected `level` to be a kind of [Symbol] but was Fixnum
|
59
|
+
from /usr/home/tyler/source/github/ruby/typedeaf/lib/typedeaf.rb:58:in `type_validation!'
|
30
60
|
[7] pry(main)>
|
61
|
+
|
31
62
|
```
|
32
63
|
|
64
|
+
#### Passing blocks to Typedeaf code
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
require 'typedeaf'
|
68
|
+
|
69
|
+
class Logger
|
70
|
+
include Typedeaf
|
71
|
+
|
72
|
+
define :log, message: String, block: Proc do
|
73
|
+
puts "Loggin: #{message}"
|
74
|
+
block.call
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
Logger.new.log('hello world') do
|
79
|
+
puts 'called back!'
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
## Caveats
|
84
|
+
|
85
|
+
Here's a list of caveats, or things that don't quite work, with Typedeaf:
|
86
|
+
|
87
|
+
* Typedeaf methods cannot use `return` or `break`, since they're technically
|
88
|
+
Ruby blocks and must follow the rules defined for block behaviors
|
89
|
+
* `yield` will not properly work, if you want to pass blocks into your
|
90
|
+
Typedeafed method, see the above usage example
|
91
|
+
|
33
92
|
|
34
93
|
## Installation
|
35
94
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'bench_helper'
|
2
|
+
|
3
|
+
class MissingArgs
|
4
|
+
include Typedeaf
|
5
|
+
|
6
|
+
def method_with_params(buffer)
|
7
|
+
buffer.size + rand + rand
|
8
|
+
end
|
9
|
+
|
10
|
+
define :typedeaf_with_params, buffer: String do
|
11
|
+
buffer.size + rand + rand
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
blk = lambda do |buffer|
|
16
|
+
buffer.size + rand + rand
|
17
|
+
end
|
18
|
+
|
19
|
+
p = MissingArgs.new
|
20
|
+
|
21
|
+
|
22
|
+
Benchmark.ips do |x|
|
23
|
+
x.report('typedeaf method') { begin; p.typedeaf_with_params; rescue; end; }
|
24
|
+
x.report('normal method') { begin; p.method_with_params; rescue; end; }
|
25
|
+
x.report('a simple proc') { begin; blk.(); rescue; end; }
|
26
|
+
x.compare!
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
As of: 790130d
|
2
|
+
|
3
|
+
Calculating -------------------------------------
|
4
|
+
typedeaf method 2292 i/100ms
|
5
|
+
normal method 2889 i/100ms
|
6
|
+
a simple proc 2594 i/100ms
|
7
|
+
-------------------------------------------------
|
8
|
+
typedeaf method 27691.2 (±12.6%) i/s - 137520 in 5.057106s
|
9
|
+
normal method 34595.8 (±12.9%) i/s - 170451 in 5.028954s
|
10
|
+
a simple proc 31650.1 (±15.4%) i/s - 155640 in 5.058063s
|
11
|
+
|
12
|
+
Comparison:
|
13
|
+
normal method: 34595.8 i/s
|
14
|
+
a simple proc: 31650.1 i/s - 1.09x slower
|
15
|
+
typedeaf method: 27691.2 i/s - 1.25x slower
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'bench_helper'
|
2
|
+
|
3
|
+
class Parametered
|
4
|
+
include Typedeaf
|
5
|
+
|
6
|
+
def method_with_params(buffer)
|
7
|
+
buffer.size + rand + rand
|
8
|
+
end
|
9
|
+
|
10
|
+
define :typedeaf_with_params, buffer: String do
|
11
|
+
buffer.size + rand + rand
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
blk = Proc.new do |buffer|
|
16
|
+
buffer.size + rand + rand
|
17
|
+
end
|
18
|
+
|
19
|
+
p = Parametered.new
|
20
|
+
|
21
|
+
Benchmark.ips do |x|
|
22
|
+
x.report('typedeaf method') { |i| p.typedeaf_with_params(i.to_s) }
|
23
|
+
x.report('normal method') { |i| p.method_with_params(i.to_s) }
|
24
|
+
x.report('a simple proc') { |i| blk.(i.to_s) }
|
25
|
+
x.compare!
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
As of: c4f25f0
|
2
|
+
|
3
|
+
Calculating -------------------------------------
|
4
|
+
typedeaf method 5197 i/100ms
|
5
|
+
normal method 20996 i/100ms
|
6
|
+
a simple proc 20478 i/100ms
|
7
|
+
-------------------------------------------------
|
8
|
+
typedeaf method 357074776.5 (±25.7%) i/s - 675537242 in 2.718540s
|
9
|
+
normal method 6571250036.9 (±28.7%) i/s - 5290845028 in 1.359025s
|
10
|
+
a simple proc 6206517128.9 (±28.5%) i/s - 5476574886 in 1.375870s
|
11
|
+
|
12
|
+
Comparison:
|
13
|
+
normal method: 6571250036.9 i/s
|
14
|
+
a simple proc: 6206517128.9 i/s - 1.06x slower
|
15
|
+
typedeaf method: 357074776.5 i/s - 18.40x slower
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'bench_helper'
|
2
|
+
|
3
|
+
class Simple
|
4
|
+
include Typedeaf
|
5
|
+
def method_computer
|
6
|
+
rand + rand
|
7
|
+
end
|
8
|
+
|
9
|
+
define :typedeaf_computer do
|
10
|
+
rand + rand
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
s = Simple.new
|
15
|
+
blk = Proc.new do
|
16
|
+
rand + rand
|
17
|
+
end
|
18
|
+
|
19
|
+
Benchmark.ips do |x|
|
20
|
+
x.report('typedeaf method') { s.typedeaf_computer }
|
21
|
+
x.report('normal method') { s.method_computer }
|
22
|
+
x.report('a simple proc') { blk.() }
|
23
|
+
x.compare!
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
As of: af96558
|
2
|
+
|
3
|
+
Calculating -------------------------------------
|
4
|
+
typedeaf method 12808 i/100ms
|
5
|
+
normal method 19336 i/100ms
|
6
|
+
a simple proc 18550 i/100ms
|
7
|
+
-------------------------------------------------
|
8
|
+
typedeaf method 461399.4 (±11.7%) i/s - 2267016 in 5.001402s
|
9
|
+
normal method 1050875.4 (±15.6%) i/s - 5104704 in 5.008836s
|
10
|
+
a simple proc 912593.9 (±15.8%) i/s - 4433450 in 5.012059s
|
11
|
+
|
12
|
+
Comparison:
|
13
|
+
normal method: 1050875.4 i/s
|
14
|
+
a simple proc: 912593.9 i/s - 1.15x slower
|
15
|
+
typedeaf method: 461399.4 i/s - 2.28x slower
|
16
|
+
|
data/lib/typedeaf.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
require 'typedeaf/
|
1
|
+
require 'typedeaf/classmethods'
|
2
|
+
require 'typedeaf/instancemethods'
|
2
3
|
require "typedeaf/version"
|
3
4
|
|
4
5
|
module Typedeaf
|
@@ -7,68 +8,6 @@ module Typedeaf
|
|
7
8
|
base.extend(ClassMethods)
|
8
9
|
end
|
9
10
|
|
10
|
-
module InstanceMethods
|
11
|
-
def __typedeaf_varstack__
|
12
|
-
if @__typedeaf_varstack__.nil?
|
13
|
-
@__typedeaf_varstack__ = []
|
14
|
-
end
|
15
|
-
return @__typedeaf_varstack__
|
16
|
-
end
|
17
|
-
|
18
|
-
def method_missing(sym, *args)
|
19
|
-
# We only want to peek at the stack if we have no args (i.e. not trying
|
20
|
-
# to make a method call
|
21
|
-
if args.empty? && !(__typedeaf_varstack__.empty?)
|
22
|
-
params, values = __typedeaf_varstack__.last
|
23
|
-
# The top of our stack contains something that we want
|
24
|
-
return values[sym] if params[sym]
|
25
|
-
end
|
26
|
-
|
27
|
-
return super
|
28
|
-
end
|
29
|
-
|
30
|
-
def positional_validation!(params, args)
|
31
|
-
if params.size != args.size
|
32
|
-
raise ArgumentError, "wrong number of arguments (#{args.size} for #{params.size}"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def type_validation!(expected, param, value)
|
37
|
-
unless value.is_a?(expected)
|
38
|
-
raise InvalidTypeException,
|
39
|
-
"Expected `#{param}` to be a kind of #{expected} but was #{value.class}"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
module ClassMethods
|
45
|
-
def define(method_sym, params={}, &block)
|
46
|
-
if block.nil?
|
47
|
-
raise MissingMethodException,
|
48
|
-
"You must provide a block for the #{method_sym} body"
|
49
|
-
end
|
50
|
-
|
51
|
-
define_method(method_sym) do |*args|
|
52
|
-
positional_validation!(params.keys, args)
|
53
|
-
|
54
|
-
param_indices = {}
|
55
|
-
params.each.with_index do |(param, type), index|
|
56
|
-
value = args[index]
|
57
|
-
type_validation!(type, param, value)
|
58
|
-
param_indices[param] = value
|
59
|
-
end
|
60
|
-
__typedeaf_varstack__ << [params, param_indices]
|
61
|
-
|
62
|
-
begin
|
63
|
-
instance_exec(*args, &block)
|
64
|
-
ensure
|
65
|
-
__typedeaf_varstack__.pop
|
66
|
-
end
|
67
|
-
end
|
68
|
-
return self
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
11
|
# Install the Typedeaf methods onto Class to be used everywhere
|
73
12
|
def self.global_install
|
74
13
|
Class.class_eval do
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
|
3
|
+
require 'typedeaf/arguments'
|
4
|
+
require 'typedeaf/errors'
|
5
|
+
|
6
|
+
module Typedeaf
|
7
|
+
module ClassMethods
|
8
|
+
def default(value, *types)
|
9
|
+
return Typedeaf::Arguments::DefaultArgument.new(value, *types)
|
10
|
+
end
|
11
|
+
|
12
|
+
def promise(method_sym, params={}, &block)
|
13
|
+
future(method_sym, params, primitive=Concurrent::Promise, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def future(method_sym, params={}, primitive=Concurrent::Future, &block)
|
17
|
+
__typedeaf_validate_body_for(method_sym, block)
|
18
|
+
__typedeaf_method_parameters__[method_sym] = params
|
19
|
+
|
20
|
+
define_method(method_sym) do |*args, &blk|
|
21
|
+
__typedeaf_handle_nested_block(params, args, blk)
|
22
|
+
__typedeaf_handle_default_parameters(params, args)
|
23
|
+
__typedeaf_validate_positionals(params, args)
|
24
|
+
|
25
|
+
stack_element = [method_sym, __typedeaf_validate_types(params, args)]
|
26
|
+
primitive.new do
|
27
|
+
# We're inserting into the varstack within the future to make sure
|
28
|
+
# we're using the right thread+instance combination
|
29
|
+
__typedeaf_varstack__ << stack_element
|
30
|
+
begin
|
31
|
+
instance_exec(&block)
|
32
|
+
ensure
|
33
|
+
__typedeaf_varstack__.pop
|
34
|
+
end
|
35
|
+
end.execute
|
36
|
+
end
|
37
|
+
|
38
|
+
return self
|
39
|
+
end
|
40
|
+
|
41
|
+
def define(method_sym, params={}, &block)
|
42
|
+
params = params.freeze
|
43
|
+
__typedeaf_validate_body_for(method_sym, block)
|
44
|
+
__typedeaf_method_parameters__[method_sym] = params
|
45
|
+
|
46
|
+
define_method(method_sym) do |*args, &blk|
|
47
|
+
# Optimization, if we're a parameter-less method, just pass right
|
48
|
+
# through without any checks whatsoever
|
49
|
+
if params.empty?
|
50
|
+
return instance_exec(&block)
|
51
|
+
end
|
52
|
+
|
53
|
+
__typedeaf_handle_nested_block(params, args, blk)
|
54
|
+
__typedeaf_handle_default_parameters(params, args)
|
55
|
+
__typedeaf_validate_positionals(params, args)
|
56
|
+
|
57
|
+
__typedeaf_varstack__ << [method_sym,
|
58
|
+
__typedeaf_validate_types(params, args)]
|
59
|
+
|
60
|
+
begin
|
61
|
+
instance_exec(&block)
|
62
|
+
ensure
|
63
|
+
__typedeaf_varstack__.pop
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return self
|
68
|
+
end
|
69
|
+
|
70
|
+
def __typedeaf_method_parameters__
|
71
|
+
if @__typedeaf_method_parameters__.nil?
|
72
|
+
@__typedeaf_method_parameters__ = {}
|
73
|
+
end
|
74
|
+
|
75
|
+
return @__typedeaf_method_parameters__
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def __typedeaf_validate_body_for(method, block)
|
81
|
+
if block.nil?
|
82
|
+
raise MissingMethodException,
|
83
|
+
"You must provide a block for the #{method} body"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'typedeaf/arguments'
|
4
|
+
require 'typedeaf/errors'
|
5
|
+
|
6
|
+
module Typedeaf
|
7
|
+
module InstanceMethods
|
8
|
+
def method_missing(varname, *args)
|
9
|
+
# We only want to peek at the stack if we have no args (i.e. not trying
|
10
|
+
# to make a method call
|
11
|
+
if args.empty?
|
12
|
+
element = __typedeaf_varstack__.last
|
13
|
+
|
14
|
+
# If our stack is empty then we'll get a nil element back, making sure
|
15
|
+
# we only call into __typedeaf_varstack__ once for the #method_missing
|
16
|
+
# invocation
|
17
|
+
unless element.nil?
|
18
|
+
method_name = element.first
|
19
|
+
# The top of our stack contains something that we want
|
20
|
+
if self.class.__typedeaf_method_parameters__[method_name][varname]
|
21
|
+
return element.last[varname]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
return super
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Access the current thread's and instance's varstack
|
32
|
+
#
|
33
|
+
# Since we're inside of an object instance already, we should make sure
|
34
|
+
# that we can isolate the method's varstack for our current thread and
|
35
|
+
# instance together.
|
36
|
+
#
|
37
|
+
# Instaed of using a thread local by itself, which would not provide the
|
38
|
+
# cross-object isolation, and instead of using just an instance variable,
|
39
|
+
# which would require thread-safety locks, serializing all calls into and
|
40
|
+
# out of the instance
|
41
|
+
#
|
42
|
+
# @return [Array] variable stack
|
43
|
+
def __typedeaf_varstack__
|
44
|
+
if @__typedeaf_varstack_id__.nil?
|
45
|
+
@__typedeaf_varstack_id__ = "typedeaf_varstack_#{self.object_id}".to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
if Thread.current[@__typedeaf_varstack_id__].nil?
|
49
|
+
Thread.current[@__typedeaf_varstack_id__] = []
|
50
|
+
end
|
51
|
+
return Thread.current[@__typedeaf_varstack_id__]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Determine whether the supplied value is an instance of the given class
|
55
|
+
#
|
56
|
+
# @param [Object] value
|
57
|
+
# @param [Class] type Any class
|
58
|
+
def __typedeaf_valid_type?(value, type)
|
59
|
+
return value.is_a? type
|
60
|
+
end
|
61
|
+
|
62
|
+
# Valida
|
63
|
+
def __typedeaf_validate_types(parameters, args)
|
64
|
+
# We need to walk through the list of parameters and their types and
|
65
|
+
# perform type checking on each of them
|
66
|
+
parameter_indices = {}
|
67
|
+
index = 0
|
68
|
+
parameters.each do |param, types|
|
69
|
+
value = args[index]
|
70
|
+
index = index + 1
|
71
|
+
|
72
|
+
__typedeaf_validate_types_for(param, value, types)
|
73
|
+
|
74
|
+
# Adding the index of this parameter's value to our Hash so we can
|
75
|
+
# properly fish it back out when the method_missing magic is being
|
76
|
+
# invoked from within the block
|
77
|
+
parameter_indices[param] = value
|
78
|
+
end
|
79
|
+
|
80
|
+
return parameter_indices
|
81
|
+
end
|
82
|
+
|
83
|
+
# Validate the expected types for a param
|
84
|
+
def __typedeaf_validate_types_for(param, value, types)
|
85
|
+
validated = false
|
86
|
+
|
87
|
+
# If we've received a DefaultArgument, we need to dig into it to get our
|
88
|
+
# types to check back out
|
89
|
+
if types.is_a? Typedeaf::Arguments::DefaultArgument
|
90
|
+
types = types.types
|
91
|
+
end
|
92
|
+
|
93
|
+
if types.is_a? Array
|
94
|
+
types.each do |type|
|
95
|
+
validated = __typedeaf_valid_type? value, type
|
96
|
+
break if validated
|
97
|
+
end
|
98
|
+
else
|
99
|
+
validated = __typedeaf_valid_type? value, types
|
100
|
+
end
|
101
|
+
|
102
|
+
unless validated
|
103
|
+
raise InvalidTypeException,
|
104
|
+
"Expected `#{param}` to be a kind of #{types} but was #{value.class}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# If we've been given a block, and it's in the params list properly,
|
109
|
+
# then we should just add it to the args as a "positional" argument
|
110
|
+
def __typedeaf_handle_nested_block(parameters, args, block)
|
111
|
+
if block && parameters[:block]
|
112
|
+
args << block
|
113
|
+
end
|
114
|
+
return nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def __typedeaf_handle_default_parameters(parameters, args)
|
118
|
+
# If both parameters and args are of equal size, then we don't have any
|
119
|
+
# defaulted parameters that we need to insert
|
120
|
+
return unless parameters.size > args.size
|
121
|
+
|
122
|
+
# Check to see if we have any defaulted parameters
|
123
|
+
parameters.each do |name, argument|
|
124
|
+
# Unless it's a special kind of argument, skip it
|
125
|
+
next unless argument.is_a? Typedeaf::Arguments::DefaultArgument
|
126
|
+
|
127
|
+
args << argument.value
|
128
|
+
end
|
129
|
+
|
130
|
+
return nil
|
131
|
+
end
|
132
|
+
|
133
|
+
# Validate that we have the right number of positional arguments
|
134
|
+
#
|
135
|
+
# This is only really needed to make sure we're behaving the same
|
136
|
+
# was as natively defined method would
|
137
|
+
def __typedeaf_validate_positionals(parameters, args)
|
138
|
+
if parameters.size != args.size
|
139
|
+
raise ArgumentError,
|
140
|
+
"wrong number of arguments (#{args.size} for #{parameters.size})"
|
141
|
+
end
|
142
|
+
return nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/lib/typedeaf/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
data/spec/typedeaf_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe Typedeaf do
|
|
12
12
|
it { should respond_to :define }
|
13
13
|
end
|
14
14
|
|
15
|
-
context 'defining
|
15
|
+
context 'defining instance methods' do
|
16
16
|
subject(:instance) { klass.new }
|
17
17
|
|
18
18
|
context 'defining a method with an invalid block' do
|
@@ -25,7 +25,6 @@ describe Typedeaf do
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
28
|
context 'defining a method with no arguments' do
|
30
29
|
before :each do
|
31
30
|
klass.class_eval do
|
@@ -42,8 +41,7 @@ describe Typedeaf do
|
|
42
41
|
end
|
43
42
|
end
|
44
43
|
|
45
|
-
|
46
|
-
context 'defining a method with arguments' do
|
44
|
+
context 'defining a method with positional arguments' do
|
47
45
|
before :each do
|
48
46
|
klass.class_eval do
|
49
47
|
define :log, message: String do
|
@@ -69,5 +67,141 @@ describe Typedeaf do
|
|
69
67
|
}.to raise_error(Typedeaf::InvalidTypeException)
|
70
68
|
end
|
71
69
|
end
|
70
|
+
|
71
|
+
context 'defining a method with multiple acceptable types' do
|
72
|
+
before :each do
|
73
|
+
klass.class_eval do
|
74
|
+
define :log, message: [String, Symbol] do
|
75
|
+
"hello #{message}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it { should respond_to :log }
|
81
|
+
it 'should work for different types' do
|
82
|
+
expect(instance.log(:world)).to eql('hello world')
|
83
|
+
expect(instance.log('world')).to eql('hello world')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'defining a method with default arguments' do
|
88
|
+
before :each do
|
89
|
+
klass.class_eval do
|
90
|
+
define :log, message: String, level: default(:debug, Symbol) do
|
91
|
+
[message, level].map(&:to_s).join(' ')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it { should respond_to :log }
|
97
|
+
it 'a default call should use the arguments to create a result' do
|
98
|
+
expect(instance.log('hello')).to eql('hello debug')
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should be callable multiple times in a row' do
|
102
|
+
3.times do
|
103
|
+
expect(instance.log('hello')).to eql('hello debug')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'defining a recursing method' do
|
109
|
+
before :each do
|
110
|
+
klass.class_eval do
|
111
|
+
define :log, message: [String, Array] do
|
112
|
+
if message.is_a? Array
|
113
|
+
next message.map { |m| self.log(m) }
|
114
|
+
end
|
115
|
+
"hello #{message}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it { should respond_to :log }
|
121
|
+
|
122
|
+
it 'should generate the right recursive behavior' do
|
123
|
+
expect(instance.log(['tom', 'jerry'])).to eql(['hello tom',
|
124
|
+
'hello jerry'])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'defining a method which accepts a block' do
|
129
|
+
before :each do
|
130
|
+
klass.class_eval do
|
131
|
+
define :log, message: String, block: Proc do
|
132
|
+
block.call
|
133
|
+
'hello proc'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
it { should respond_to :log }
|
139
|
+
|
140
|
+
it 'should return and yield the right thing' do
|
141
|
+
called = false
|
142
|
+
result = nil
|
143
|
+
result = instance.log('hello') do
|
144
|
+
called = true
|
145
|
+
end
|
146
|
+
expect(result).to eql('hello proc')
|
147
|
+
expect(called).to be_truthy
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'defining a future method' do
|
152
|
+
before :each do
|
153
|
+
klass.class_eval do
|
154
|
+
future :log, message: String do
|
155
|
+
message
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it { should respond_to :log }
|
161
|
+
|
162
|
+
context 'the method result' do
|
163
|
+
let(:msg) { 'hello' }
|
164
|
+
subject(:result) { instance.log(msg) }
|
165
|
+
|
166
|
+
it { should be_kind_of Concurrent::Future }
|
167
|
+
|
168
|
+
it 'should successfully execute' do
|
169
|
+
expect(result.value).to eql msg
|
170
|
+
expect(result.state).to eql(:fulfilled), "Failure: #{result.reason}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'with the wrong parameter types' do
|
175
|
+
it 'should raise immediately' do
|
176
|
+
expect {
|
177
|
+
instance.log(:failboat)
|
178
|
+
}.to raise_error(Typedeaf::InvalidTypeException)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'defining a promise method' do
|
184
|
+
before :each do
|
185
|
+
klass.class_eval do
|
186
|
+
promise :log, message: String do
|
187
|
+
message
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
it { should respond_to :log }
|
193
|
+
|
194
|
+
context 'the method result' do
|
195
|
+
let(:msg) { 'hello' }
|
196
|
+
subject(:result) { instance.log(msg) }
|
197
|
+
|
198
|
+
it { should be_kind_of Concurrent::Promise }
|
199
|
+
|
200
|
+
it 'should successfully execute' do
|
201
|
+
expect(result.value).to eql msg
|
202
|
+
expect(result.state).to eql(:fulfilled), "Failure: #{result.reason}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
72
206
|
end
|
73
207
|
end
|
data/typedeaf.gemspec
CHANGED
@@ -17,6 +17,5 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.
|
21
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
20
|
+
spec.add_dependency 'concurrent-ruby', '~> 0.7.0'
|
22
21
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: typedeaf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- R. Tyler Croy
|
@@ -9,28 +9,18 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2014-10-
|
12
|
+
date: 2014-10-27 00:00:00 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: concurrent-ruby
|
16
16
|
requirement: &id001 !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
18
|
- - ~>
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
21
|
-
type: :
|
20
|
+
version: 0.7.0
|
21
|
+
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: *id001
|
24
|
-
- !ruby/object:Gem::Dependency
|
25
|
-
name: rake
|
26
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
27
|
-
requirements:
|
28
|
-
- - ~>
|
29
|
-
- !ruby/object:Gem::Version
|
30
|
-
version: "10.0"
|
31
|
-
type: :development
|
32
|
-
prerelease: false
|
33
|
-
version_requirements: *id002
|
34
24
|
description:
|
35
25
|
email:
|
36
26
|
- tyler@monkeypox.org
|
@@ -43,12 +33,23 @@ extra_rdoc_files: []
|
|
43
33
|
files:
|
44
34
|
- .gitignore
|
45
35
|
- .rspec
|
36
|
+
- .travis.yml
|
46
37
|
- Gemfile
|
47
38
|
- LICENSE.txt
|
48
39
|
- README.md
|
49
40
|
- Rakefile
|
41
|
+
- benchmarks/bench_helper.rb
|
42
|
+
- benchmarks/missing_arguments.rb
|
43
|
+
- benchmarks/missing_arguments.txt
|
44
|
+
- benchmarks/parameter_calls.rb
|
45
|
+
- benchmarks/parameter_calls.txt
|
46
|
+
- benchmarks/simple_calls.rb
|
47
|
+
- benchmarks/simple_calls.txt
|
50
48
|
- lib/typedeaf.rb
|
49
|
+
- lib/typedeaf/arguments.rb
|
50
|
+
- lib/typedeaf/classmethods.rb
|
51
51
|
- lib/typedeaf/errors.rb
|
52
|
+
- lib/typedeaf/instancemethods.rb
|
52
53
|
- lib/typedeaf/version.rb
|
53
54
|
- spec/spec_helper.rb
|
54
55
|
- spec/typedeaf_spec.rb
|
@@ -65,13 +66,13 @@ require_paths:
|
|
65
66
|
- lib
|
66
67
|
required_ruby_version: !ruby/object:Gem::Requirement
|
67
68
|
requirements:
|
68
|
-
- &
|
69
|
+
- &id002
|
69
70
|
- ">="
|
70
71
|
- !ruby/object:Gem::Version
|
71
72
|
version: "0"
|
72
73
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
74
|
requirements:
|
74
|
-
- *
|
75
|
+
- *id002
|
75
76
|
requirements: []
|
76
77
|
|
77
78
|
rubyforge_project:
|