invokr 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +69 -0
- data/Rakefile +22 -0
- data/bin/rake +18 -0
- data/bin/test_runner +19 -0
- data/invokr.gemspec +25 -0
- data/lib/invokr.rb +57 -0
- data/lib/invokr/builder.rb +116 -0
- data/lib/invokr/dependency_injection.rb +30 -0
- data/lib/invokr/errors.rb +63 -0
- data/lib/invokr/invocation.rb +22 -0
- data/lib/invokr/version.rb +3 -0
- data/test/block_args_test.rb +64 -0
- data/test/dependency_injection_example_test.rb +41 -0
- data/test/invokr_test.rb +12 -0
- data/test/keyword_args_test.rb +70 -0
- data/test/optional_args_test.rb +66 -0
- data/test/query_test.rb +18 -0
- data/test/required_args_test.rb +41 -0
- data/test/test_helper.rb +59 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d0174897be54196672b5fc187801b8afb7b950a5
|
4
|
+
data.tar.gz: 4b3b4c3656ffb373aab0cfdd56060689dbb7a40b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0f5edb40a999afc101c009ce040a82170c3356ffe25b79150e590dc7b6813ac84db2d9be765ac74764c8f0102cd57cc00a02ae1376fa776dd6e593b1865a106e
|
7
|
+
data.tar.gz: 4c8001a382bb203d173ef4558f690678987c9ad9d5113f7e0dbad775e0e24748b291e057ac7f2abbb29f47cc9f9ca99d7a2c68dfd3fce05f658c5dac401d2cc6
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
tags
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 ntl
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Invokr
|
2
|
+
|
3
|
+
Invoke methods with a consistent Hash interface. Useful for metaprogramming.
|
4
|
+
|
5
|
+
## Basic Usage
|
6
|
+
|
7
|
+
Let's say you've got a method you want to call:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class BankAccount
|
11
|
+
def add_transaction(amount, account_id:, description: '')
|
12
|
+
# adds transaction
|
13
|
+
end
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
You can invoke this method through the class interface with pure ruby. But sometimes you've got a Hash containing the parameters. This is where Invokr comes in:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
bank_account = BankAccount.new
|
21
|
+
params = JSON.parse http_request.response_body
|
22
|
+
Invokr.invoke method: :add_transaction, on: bank_account, with: params
|
23
|
+
```
|
24
|
+
|
25
|
+
Behind the scenes, Invokr figured out how to translate that `Hash` into a method signature compatible with `BankAccount#add_transaction`.
|
26
|
+
|
27
|
+
## Querying
|
28
|
+
|
29
|
+
Want to investigate the arguments of a method?
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
meth = Invokr.query_method bank_account.method(:add_transaction)
|
33
|
+
```
|
34
|
+
|
35
|
+
This will return an object that you can use to inspect the optional/required dependencies of a method:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
meth.required_dependencies
|
39
|
+
=> [:amount, :account_id]
|
40
|
+
meth.optional_dependencies
|
41
|
+
=> [:description]
|
42
|
+
```
|
43
|
+
|
44
|
+
## Limitations
|
45
|
+
|
46
|
+
Currently, more than one optional positional argument isn't supported. Consider:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
def my_method arg1 = 'foo', arg2 = 'bar'
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
Without knowing how to parse the source code for `#my_method`, Invokr couldn't know what the default values are. And even if I brought in e.g. [ruby_parser](https://github.com/seattlerb/ruby_parser), I'd have to support lazy evaluation, for when you supply a method or constant as the default. This complexity is completely unneccessary when using keyword arguments, so I suggest using that approach for multiple defaults.
|
54
|
+
|
55
|
+
## Pre-keyword argument hash defaults
|
56
|
+
|
57
|
+
Before ruby 2.x introduced keyword arguments, it was common to end your method signature with a default hash, e.g. `def my_method args = {}`. Invoker supports this by building a Hash out of all the unused arguments you passed in, and passing *that* into the optional argument.
|
58
|
+
|
59
|
+
## Dependency injection
|
60
|
+
|
61
|
+
One of the use cases for Invokr is building abstract factories. In this case, you want to inspect the method signature of `Object#initialize`, but actually pass `.new` to the class to have it allocate memory and invoke the initializer for you. Since this is a weird case, there's some support to make building a dependency injector much easier, just make sure you explictily `require "invokr/dependency_injection"` and then check out `test/dependency_injection_example_test.rb` for how it is used. Basically, your factory object just needs to implement a method called `resolve` that takes in the name of a dependency that maps to a parameter on the `#initialize` method for the class you're trying to build out.
|
62
|
+
|
63
|
+
## Contributing
|
64
|
+
|
65
|
+
1. Fork it ( https://github.com/[my-github-username]/invokr/fork )
|
66
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
67
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
68
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
69
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
desc "Update ctags"
|
4
|
+
task :ctags do
|
5
|
+
`ctags -R --languages=Ruby --totals -f tags`
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Test against all ruby versions"
|
9
|
+
task :rbenv_suite do
|
10
|
+
%w(2.1.2 2.0.0-p481 1.9.3-p545).each do |ruby_ver|
|
11
|
+
child_pid = fork do
|
12
|
+
ruby_path = File.join ENV['RBENV_ROOT'], 'shims/ruby'
|
13
|
+
cmd = "#{ruby_path} bin/test_runner ; echo"
|
14
|
+
puts "Testing #{ruby_ver}"
|
15
|
+
env = { 'RBENV_VERSION' => ruby_ver }
|
16
|
+
exec env, cmd
|
17
|
+
end
|
18
|
+
Process.wait2 child_pid
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
task :default => :rbenv_suite
|
data/bin/rake
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rake' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
load "bin/test_runner" and exit 0 if ARGV.empty?
|
10
|
+
|
11
|
+
require 'pathname'
|
12
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
require 'rubygems'
|
16
|
+
require 'bundler/setup'
|
17
|
+
|
18
|
+
load Gem.bin_path('rake', 'rake')
|
data/bin/test_runner
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Invokr
|
4
|
+
module Runner
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def run!
|
8
|
+
root = File.expand_path '../..', __FILE__
|
9
|
+
|
10
|
+
load File.join(root, 'test/test_helper.rb')
|
11
|
+
|
12
|
+
Dir[File.join(root, 'test/**/*_test.rb')].each do |test_file|
|
13
|
+
load test_file
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Invokr::Runner.run!
|
data/invokr.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'invokr/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "invokr"
|
8
|
+
spec.version = Invokr::VERSION
|
9
|
+
spec.authors = %w(ntl)
|
10
|
+
spec.email = %w(nathanladd+github@gmail.com)
|
11
|
+
spec.summary = %q{Invoke methods with a consistent Hash interface.}
|
12
|
+
spec.description = %q{Invoke methods with a consistent Hash interface. Useful for metaprogramming.}
|
13
|
+
spec.homepage = "https://github.com/ntl/invokr"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = []
|
18
|
+
spec.test_files = spec.files.grep %r{test/}
|
19
|
+
spec.require_paths = %w(lib)
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "minitest"
|
23
|
+
spec.add_development_dependency "minitest-reporters"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
data/lib/invokr.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Invokr
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def invoke args = {}
|
5
|
+
method_name, obj, hsh_args = require_arguments! args, :method, :on, :with
|
6
|
+
method = obj.method method_name
|
7
|
+
invocation = Builder.build method, hsh_args, args[:block]
|
8
|
+
invocation.invoke! obj
|
9
|
+
end
|
10
|
+
|
11
|
+
def query_method method
|
12
|
+
Method.new method
|
13
|
+
end
|
14
|
+
|
15
|
+
Method = Struct.new :method do
|
16
|
+
def dependencies
|
17
|
+
map_identifiers parameters
|
18
|
+
end
|
19
|
+
|
20
|
+
def optional_dependencies
|
21
|
+
map_identifiers select_parameters_by_type [:opt, :key]
|
22
|
+
end
|
23
|
+
|
24
|
+
def required_dependencies
|
25
|
+
map_identifiers select_parameters_by_type [:req, :keyreq]
|
26
|
+
end
|
27
|
+
|
28
|
+
def parameters
|
29
|
+
method.parameters
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def select_parameters_by_type types
|
35
|
+
parameters.select do |type, _| types.include? type end
|
36
|
+
end
|
37
|
+
|
38
|
+
def map_identifiers parameters
|
39
|
+
parameters.map do |_, identifier| identifier end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def require_arguments! hsh, *args
|
46
|
+
found_args, missing_args = args.partition do |arg|
|
47
|
+
hsh.has_key? arg
|
48
|
+
end
|
49
|
+
raise InputError.new missing_args unless missing_args.empty?
|
50
|
+
found_args.map { |arg| hsh.fetch arg }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
require_relative 'invokr/builder'
|
55
|
+
require_relative 'invokr/errors'
|
56
|
+
require_relative 'invokr/invocation'
|
57
|
+
require_relative 'invokr/version'
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Invokr
|
2
|
+
class Builder
|
3
|
+
def self.build *args
|
4
|
+
builder = new *args
|
5
|
+
builder.build
|
6
|
+
end
|
7
|
+
|
8
|
+
attr :argument_names, :injector, :method, :missing_args, :unused_args
|
9
|
+
|
10
|
+
def initialize method, injector, implicit_block
|
11
|
+
@argument_names = method.parameters.map &:last
|
12
|
+
@injector = injector
|
13
|
+
@method = method
|
14
|
+
@opt_arg_name = nil
|
15
|
+
|
16
|
+
@block_arg = nil
|
17
|
+
@implicit_block = implicit_block
|
18
|
+
@keyword_args = {}
|
19
|
+
@positional_args = []
|
20
|
+
@missing_args = []
|
21
|
+
|
22
|
+
set_unused_args
|
23
|
+
end
|
24
|
+
|
25
|
+
def build
|
26
|
+
handle_args!
|
27
|
+
check_for_unused_args!
|
28
|
+
check_for_missing_args!
|
29
|
+
build_invocation
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_invocation
|
33
|
+
@block_arg = @implicit_block if @implicit_block
|
34
|
+
Invocation.new method.name, @positional_args, @keyword_args, @block_arg
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_args!
|
38
|
+
method.parameters.each do |type, identifier|
|
39
|
+
send "handle_#{type}_arg", identifier
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def handle_req_arg identifier
|
44
|
+
arg = injector.fetch identifier do missing_argument! identifier end
|
45
|
+
@positional_args << arg
|
46
|
+
end
|
47
|
+
|
48
|
+
def handle_opt_arg identifier
|
49
|
+
optional_arg_error! identifier if hit_opt_arg?
|
50
|
+
@opt_arg_name = identifier
|
51
|
+
arg = injector.fetch identifier do
|
52
|
+
build_hash_from_extra_args or return
|
53
|
+
end
|
54
|
+
@positional_args << arg
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle_keyreq_arg identifier
|
58
|
+
arg = injector.fetch identifier do missing_argument! identifier end
|
59
|
+
@keyword_args[identifier] = arg
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_key_arg identifier
|
63
|
+
return unless injector.has_key? identifier
|
64
|
+
@keyword_args[identifier] = injector[identifier]
|
65
|
+
end
|
66
|
+
|
67
|
+
def handle_block_arg identifier
|
68
|
+
if injector.has_key? identifier and @implicit_block
|
69
|
+
unused_args << identifier and return
|
70
|
+
end
|
71
|
+
@block_arg = injector.fetch identifier do
|
72
|
+
@implicit_block or missing_argument! identifier
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def hit_opt_arg?
|
77
|
+
@opt_arg_name ? true : false
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_unused_args
|
81
|
+
@unused_args = injector.keys.flat_map do |hsh_arg|
|
82
|
+
argument_names.include?(hsh_arg) ? [] : [hsh_arg]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_hash_from_extra_args
|
87
|
+
return nil if unused_args.empty?
|
88
|
+
hsh = {}
|
89
|
+
unused_args.each do |arg| hsh[arg] = injector.fetch arg end
|
90
|
+
unused_args.clear
|
91
|
+
hsh
|
92
|
+
end
|
93
|
+
|
94
|
+
def check_for_unused_args!
|
95
|
+
return if unused_args.empty?
|
96
|
+
raise ExtraArgumentsError.new self, unused_args
|
97
|
+
end
|
98
|
+
|
99
|
+
def check_for_missing_args!
|
100
|
+
return if missing_args.empty?
|
101
|
+
raise MissingArgumentsError.new self, missing_args
|
102
|
+
end
|
103
|
+
|
104
|
+
def optional_arg_error! identifier
|
105
|
+
raise OptionalPositionalArgumentError.new(
|
106
|
+
method.name,
|
107
|
+
@opt_arg_name,
|
108
|
+
identifier,
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def missing_argument! identifier
|
113
|
+
missing_args << identifier
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Invokr
|
2
|
+
module DependencyInjection
|
3
|
+
def self.inject args = {}
|
4
|
+
klass = args.fetch :klass
|
5
|
+
resolver = args.fetch :using
|
6
|
+
injector = Injector.new resolver, klass
|
7
|
+
injector.inject
|
8
|
+
end
|
9
|
+
|
10
|
+
Injector = Struct.new :resolver, :klass do
|
11
|
+
def inject
|
12
|
+
invocation = Builder.build initializer, self, nil
|
13
|
+
invocation.method = :new
|
14
|
+
invocation.invoke! klass
|
15
|
+
end
|
16
|
+
|
17
|
+
def keys
|
18
|
+
initializer.parameters.map { |_, identifier| identifier }
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch arg, &default
|
22
|
+
resolver.resolve arg, &default
|
23
|
+
end
|
24
|
+
|
25
|
+
def initializer
|
26
|
+
klass.instance_method :initialize
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Invokr
|
2
|
+
class InputError < ArgumentError
|
3
|
+
attr :missing_args
|
4
|
+
|
5
|
+
def initialize missing_args
|
6
|
+
@missing_args = missing_args
|
7
|
+
missing_args.map! do |arg| "`#{arg}'" end
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
@message ||= build_message
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_message
|
15
|
+
prefix = "cannot invoke; missing required arguments: "
|
16
|
+
prefix << concat_missing_args
|
17
|
+
prefix
|
18
|
+
end
|
19
|
+
|
20
|
+
def concat_missing_args
|
21
|
+
return missing_args.first if missing_args.size == 1
|
22
|
+
last_arg = missing_args.pop
|
23
|
+
"#{missing_args.join ', '} and #{last_arg}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class BadArgumentsError < ArgumentError
|
28
|
+
attr :builder, :args
|
29
|
+
|
30
|
+
def initialize builder, args
|
31
|
+
@builder = builder
|
32
|
+
@args = args
|
33
|
+
end
|
34
|
+
|
35
|
+
def formatted_args
|
36
|
+
args.map { |arg| "`#{arg}'" }.join ', '
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ExtraArgumentsError < BadArgumentsError
|
41
|
+
def message
|
42
|
+
%(unused argument(s) #{formatted_args} when invoking method `#{builder.method.name}' on #{builder.method.owner.inspect})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class MissingArgumentsError < BadArgumentsError
|
47
|
+
def message
|
48
|
+
%(missing required argument(s) #{formatted_args} when invoking method `#{builder.method.name}' on #{builder.method.owner.inspect})
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class OptionalPositionalArgumentError < StandardError
|
53
|
+
attr :message
|
54
|
+
|
55
|
+
def initialize method, arg1, arg2
|
56
|
+
@message = <<-MESSAGE
|
57
|
+
method `#{method}' has optional positional argument `#{arg2}', after optional argument `#{arg1}'.
|
58
|
+
|
59
|
+
We cannot use this method because there's no way to supply an explicit value for `#{arg2}' without knowing the default value for `#{arg1}'. It's technically possible to overcome this with S-expression analysis, but a much simpler solution would be to use keyword arguments.
|
60
|
+
MESSAGE
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Invokr
|
2
|
+
Invocation = Struct.new :method, :positional_args, :keyword_args, :block_arg do
|
3
|
+
def invoke! obj
|
4
|
+
if block_arg?
|
5
|
+
obj.public_send method, *args, &block_arg
|
6
|
+
else
|
7
|
+
obj.public_send method, *args
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def block_arg?
|
12
|
+
block_arg ? true : false
|
13
|
+
end
|
14
|
+
|
15
|
+
def args
|
16
|
+
args = positional_args.dup
|
17
|
+
args << keyword_args unless keyword_args.empty?
|
18
|
+
args
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class BlockArgsTest < Minitest::Test
|
2
|
+
def test_supplying_block_argument_explicitly
|
3
|
+
actual = Invokr.invoke(
|
4
|
+
method: :block_argument,
|
5
|
+
on: TestMethodBank,
|
6
|
+
with: { album_block: -> { 'farmhouse' } },
|
7
|
+
)
|
8
|
+
|
9
|
+
assert_equal 'farmhouse', actual
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_supplying_block_argument_implicitly
|
13
|
+
actual = Invokr.invoke(
|
14
|
+
method: :block_argument,
|
15
|
+
on: TestMethodBank,
|
16
|
+
with: {},
|
17
|
+
block: -> { 'farmhouse' },
|
18
|
+
)
|
19
|
+
|
20
|
+
assert_equal 'farmhouse', actual
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_failing_to_supply_block_argument
|
24
|
+
error = assert_raises Invokr::MissingArgumentsError do
|
25
|
+
Invokr.invoke(
|
26
|
+
method: :block_argument,
|
27
|
+
on: TestMethodBank,
|
28
|
+
with: {},
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
assert_equal(
|
33
|
+
"missing required argument(s) `album_block' when invoking method `block_argument' on #<TestMethodBank:0xdeadbeef>",
|
34
|
+
error.message,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_supplying_block_argument_implicitly_and_explicitly
|
39
|
+
error = assert_raises Invokr::ExtraArgumentsError do
|
40
|
+
Invokr.invoke(
|
41
|
+
method: :block_argument,
|
42
|
+
on: TestMethodBank,
|
43
|
+
with: { album_block: -> { 'farmhouse' } },
|
44
|
+
block: -> { 'farmhouse' },
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
assert_equal(
|
49
|
+
"unused argument(s) `album_block' when invoking method `block_argument' on #<TestMethodBank:0xdeadbeef>",
|
50
|
+
error.message,
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_implicit_block
|
55
|
+
actual = Invokr.invoke(
|
56
|
+
method: :just_yields,
|
57
|
+
on: TestMethodBank,
|
58
|
+
with: {},
|
59
|
+
block: -> { 'farmhouse' },
|
60
|
+
)
|
61
|
+
|
62
|
+
assert_equal 'farmhouse', actual
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'invokr/dependency_injection'
|
2
|
+
|
3
|
+
class DependencyInjectionExampleTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@injector = TestInjector.new(
|
6
|
+
:album => 'farmhouse',
|
7
|
+
:guitarist => 'trey',
|
8
|
+
:drummer => 'fishman',
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_dependency_injection
|
13
|
+
@injector.inject TestObject
|
14
|
+
end
|
15
|
+
|
16
|
+
class TestInjector
|
17
|
+
def initialize hsh
|
18
|
+
@hsh = hsh
|
19
|
+
end
|
20
|
+
|
21
|
+
def inject klass
|
22
|
+
Invokr::DependencyInjection.inject(
|
23
|
+
:klass => klass,
|
24
|
+
:using => self,
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def resolve val
|
29
|
+
@hsh.fetch val
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class TestObject
|
34
|
+
attr :album, :guitarist
|
35
|
+
|
36
|
+
def initialize album, guitarist
|
37
|
+
@album = album
|
38
|
+
@guitarist = guitarist
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/test/invokr_test.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
class InvokrTest < Minitest::Test
|
2
|
+
def test_incorrectly_invoking
|
3
|
+
error = assert_raises Invokr::InputError do
|
4
|
+
Invokr.invoke
|
5
|
+
end
|
6
|
+
|
7
|
+
assert_equal(
|
8
|
+
"cannot invoke; missing required arguments: `method', `on' and `with'",
|
9
|
+
error.message,
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module KeywordArgsTest
|
2
|
+
class OptionalKeywordArgsTest < Minitest::Test
|
3
|
+
def setup
|
4
|
+
skip unless RUBY_VERSION >= '2.0'
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_overriding_optional_keyword_argument
|
8
|
+
actual = Invokr.invoke(
|
9
|
+
method: :optional_keyword_argument,
|
10
|
+
on: TestMethodBank,
|
11
|
+
with: { album: 'billy_breathes' },
|
12
|
+
)
|
13
|
+
|
14
|
+
assert_equal 'billy_breathes', actual
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_using_default_optional_keyword_argument
|
18
|
+
actual = Invokr.invoke(
|
19
|
+
method: :optional_keyword_argument,
|
20
|
+
on: TestMethodBank,
|
21
|
+
with: {},
|
22
|
+
)
|
23
|
+
|
24
|
+
assert_equal 'pitcher_of_nectar', actual
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_querying_optional_keyword_argument
|
28
|
+
method = Invokr.query_method TestMethodBank.method :optional_keyword_argument
|
29
|
+
|
30
|
+
assert_equal [:album], method.optional_dependencies
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class RequiredKeywordArgsTest < Minitest::Test
|
35
|
+
def setup
|
36
|
+
skip unless RUBY_VERSION >= '2.1'
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_supplying_required_keyword_argument
|
40
|
+
actual = Invokr.invoke(
|
41
|
+
method: :required_keyword_argument,
|
42
|
+
on: TestMethodBank,
|
43
|
+
with: { album: 'pitcher_of_nectar' },
|
44
|
+
)
|
45
|
+
|
46
|
+
assert_equal 'pitcher_of_nectar', actual
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_failing_to_supply_a_required_keyword_argument
|
50
|
+
error = assert_raises Invokr::MissingArgumentsError do
|
51
|
+
Invokr.invoke(
|
52
|
+
method: :required_keyword_argument,
|
53
|
+
on: TestMethodBank,
|
54
|
+
with: {},
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
assert_equal(
|
59
|
+
"missing required argument(s) `album' when invoking method `required_keyword_argument' on #<TestMethodBank:0xdeadbeef>",
|
60
|
+
error.message,
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_querying_required_keyword_argument
|
65
|
+
method = Invokr.query_method TestMethodBank.method :required_keyword_argument
|
66
|
+
|
67
|
+
assert_equal [:album], method.required_dependencies
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class OptionalArgsTest < Minitest::Test
|
2
|
+
def test_overriding_optional_argument
|
3
|
+
actual = Invokr.invoke(
|
4
|
+
method: :optional_argument,
|
5
|
+
on: TestMethodBank,
|
6
|
+
with: { album: 'rift' },
|
7
|
+
)
|
8
|
+
|
9
|
+
assert_equal 'rift', actual
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_default_optional_argument
|
13
|
+
actual = Invokr.invoke(
|
14
|
+
method: :optional_argument,
|
15
|
+
on: TestMethodBank,
|
16
|
+
with: {},
|
17
|
+
)
|
18
|
+
|
19
|
+
assert_equal 'junta', actual
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_use_extra_args_as_hash_for_optional_argument
|
23
|
+
hsh = { guitarist: 'trey' }
|
24
|
+
|
25
|
+
actual = Invokr.invoke(
|
26
|
+
method: :optional_argument,
|
27
|
+
on: TestMethodBank,
|
28
|
+
with: hsh,
|
29
|
+
)
|
30
|
+
|
31
|
+
assert_equal hsh, actual
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_cant_use_extra_args_as_hash_to_override_optional_argument
|
35
|
+
error = assert_raises Invokr::ExtraArgumentsError do
|
36
|
+
Invokr.invoke(
|
37
|
+
method: :optional_argument,
|
38
|
+
on: TestMethodBank,
|
39
|
+
with: { album: 'junta', guitarist: 'trey' },
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
assert_equal(
|
44
|
+
"unused argument(s) `guitarist' when invoking method `optional_argument' on #<TestMethodBank:0xdeadbeef>",
|
45
|
+
error.message,
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_argument_after_optional_argument_raises_error
|
50
|
+
error = assert_raises Invokr::OptionalPositionalArgumentError do
|
51
|
+
Invokr.invoke(
|
52
|
+
method: :double_optional_argument,
|
53
|
+
on: TestMethodBank,
|
54
|
+
with: {},
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
expected_error_message = <<-MESSAGE
|
59
|
+
method `double_optional_argument' has optional positional argument `album2', after optional argument `album1'.
|
60
|
+
|
61
|
+
We cannot use this method because there's no way to supply an explicit value for `album2' without knowing the default value for `album1'. It's technically possible to overcome this with S-expression analysis, but a much simpler solution would be to use keyword arguments.
|
62
|
+
MESSAGE
|
63
|
+
|
64
|
+
assert_equal expected_error_message, error.message
|
65
|
+
end
|
66
|
+
end
|
data/test/query_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
class QueryTest < Minitest::Test
|
2
|
+
def setup
|
3
|
+
method = TestMethodBank.method :one_required_one_optional_argument
|
4
|
+
@method = Invokr.query_method method
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_dependencies
|
8
|
+
assert_equal [:album, :guitarist], @method.dependencies
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_optional_dependencies
|
12
|
+
assert_equal [:guitarist], @method.optional_dependencies
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_required_dependencies
|
16
|
+
assert_equal [:album], @method.required_dependencies
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class RequiredArgsTest < Minitest::Test
|
2
|
+
def test_required_argument
|
3
|
+
actual = Invokr.invoke(
|
4
|
+
method: :required_argument,
|
5
|
+
on: TestMethodBank,
|
6
|
+
with: { album: 'junta' },
|
7
|
+
)
|
8
|
+
|
9
|
+
assert_equal 'junta', actual
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_failing_to_supply_required_arguments
|
13
|
+
error = assert_raises Invokr::MissingArgumentsError do
|
14
|
+
Invokr.invoke(
|
15
|
+
method: :multiple_required_arguments,
|
16
|
+
on: TestMethodBank,
|
17
|
+
with: {},
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_equal(
|
22
|
+
"missing required argument(s) `album', `guitarist' when invoking method `multiple_required_arguments' on #<TestMethodBank:0xdeadbeef>",
|
23
|
+
error.message,
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_refuses_to_invoke_if_unused_args_are_passed
|
28
|
+
error = assert_raises Invokr::ExtraArgumentsError do
|
29
|
+
Invokr.invoke(
|
30
|
+
method: :required_argument,
|
31
|
+
on: TestMethodBank,
|
32
|
+
with: { album: 'junta', guitarist: 'trey' },
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
assert_equal(
|
37
|
+
"unused argument(s) `guitarist' when invoking method `required_argument' on #<TestMethodBank:0xdeadbeef>",
|
38
|
+
error.message,
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
$LOAD_PATH.<< File.expand_path '../../lib', __FILE__
|
2
|
+
require 'invokr'
|
3
|
+
|
4
|
+
require 'minitest'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
require 'minitest/reporters'
|
7
|
+
|
8
|
+
Minitest::Reporters.use! Minitest::Reporters::DefaultReporter.new
|
9
|
+
|
10
|
+
module TestMethodBank
|
11
|
+
extend self
|
12
|
+
|
13
|
+
def required_argument album
|
14
|
+
album
|
15
|
+
end
|
16
|
+
|
17
|
+
def optional_argument album = 'junta'
|
18
|
+
album
|
19
|
+
end
|
20
|
+
|
21
|
+
def double_optional_argument album1 = 'junta', album2 = 'rift'
|
22
|
+
end
|
23
|
+
|
24
|
+
def block_argument &album_block
|
25
|
+
album_block.call
|
26
|
+
end
|
27
|
+
|
28
|
+
def multiple_required_arguments album, guitarist
|
29
|
+
[album, guitarist]
|
30
|
+
end
|
31
|
+
|
32
|
+
def one_required_one_optional_argument album, guitarist = 'trey'
|
33
|
+
[album, guitarist]
|
34
|
+
end
|
35
|
+
|
36
|
+
def just_yields
|
37
|
+
yield
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect
|
41
|
+
"#<#{name}:0xdeadbeef>"
|
42
|
+
end
|
43
|
+
|
44
|
+
if RUBY_VERSION >= '2.0'
|
45
|
+
module_eval <<-RB, __FILE__, __LINE__
|
46
|
+
def optional_keyword_argument album: 'pitcher_of_nectar'
|
47
|
+
album
|
48
|
+
end
|
49
|
+
RB
|
50
|
+
end
|
51
|
+
|
52
|
+
if RUBY_VERSION >= '2.1'
|
53
|
+
module_eval <<-RB, __FILE__, __LINE__
|
54
|
+
def required_keyword_argument(album:)
|
55
|
+
album
|
56
|
+
end
|
57
|
+
RB
|
58
|
+
end
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: invokr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ntl
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest-reporters
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Invoke methods with a consistent Hash interface. Useful for metaprogramming.
|
70
|
+
email:
|
71
|
+
- nathanladd+github@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- bin/rake
|
82
|
+
- bin/test_runner
|
83
|
+
- invokr.gemspec
|
84
|
+
- lib/invokr.rb
|
85
|
+
- lib/invokr/builder.rb
|
86
|
+
- lib/invokr/dependency_injection.rb
|
87
|
+
- lib/invokr/errors.rb
|
88
|
+
- lib/invokr/invocation.rb
|
89
|
+
- lib/invokr/version.rb
|
90
|
+
- test/block_args_test.rb
|
91
|
+
- test/dependency_injection_example_test.rb
|
92
|
+
- test/invokr_test.rb
|
93
|
+
- test/keyword_args_test.rb
|
94
|
+
- test/optional_args_test.rb
|
95
|
+
- test/query_test.rb
|
96
|
+
- test/required_args_test.rb
|
97
|
+
- test/test_helper.rb
|
98
|
+
homepage: https://github.com/ntl/invokr
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.3.0
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Invoke methods with a consistent Hash interface.
|
122
|
+
test_files:
|
123
|
+
- test/block_args_test.rb
|
124
|
+
- test/dependency_injection_example_test.rb
|
125
|
+
- test/invokr_test.rb
|
126
|
+
- test/keyword_args_test.rb
|
127
|
+
- test/optional_args_test.rb
|
128
|
+
- test/query_test.rb
|
129
|
+
- test/required_args_test.rb
|
130
|
+
- test/test_helper.rb
|