invokr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in invokr.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -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
@@ -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')
@@ -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!
@@ -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
@@ -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,3 @@
1
+ module Invokr
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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
@@ -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
@@ -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