typedeaf 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/rtyler/typedeaf.svg)](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:
|