detach 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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/detach.rb +106 -0
  3. data/test/test_detach.rb +142 -0
  4. metadata +44 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b5f072cc4f0f35c933ba67ede12f20dbd8bcb258
4
+ data.tar.gz: f442b9baf99c44c4a49ec2f8af5bc6bce365f50a
5
+ SHA512:
6
+ metadata.gz: 9d6f1eeb94dc88d3db16390ef1c802fb9421bee7c4fe17d06fce3ca7e3b9a50c247c7599b82254e34090d558f76d31d36f94c76968aed339557a5645a6ba54f6
7
+ data.tar.gz: 0bddec8616d667425cdae4b0c6c775cc8dadea9b863bc307125ddfd068bdeaa788fcd0b88a0660e948aeb61820f870a09514f56d0c6df2d0d5a86c935ffe0db0
data/lib/detach.rb ADDED
@@ -0,0 +1,106 @@
1
+ # The Detach mixin provides method dispatch according to argument types.
2
+ # Method definitions are separated by name and signatue, allowing for
3
+ # C++ or Java style overloading.
4
+ #
5
+ # Example:
6
+ # class Bar
7
+ # include Detach
8
+ # taking['String','String']
9
+ # def foo(a,b)
10
+ # a.upcase + b.upcase
11
+ # end
12
+ # taking['Integer','Integer']
13
+ # def foo(a=42,b)
14
+ # a * b
15
+ # end
16
+ # taking['Object','String']
17
+ # def foo(a,*b)
18
+ # b.map {|s| s.upcase + a.to_s}.join
19
+ # end
20
+ # end
21
+ module Detach
22
+ # Extends the base class with the module Detach::Types.
23
+ def self.included(base)
24
+ base.extend(Types)
25
+ end
26
+ # Provides run-time method lookup according to the types of the args.
27
+ #
28
+ # All methods matching the name are scored according to both arity and type.
29
+ # Varargs and default values are interpolated with actual values. Predefined classes
30
+ # are compared to actual classes using equality and inheritence checks.
31
+ def method_missing(name, *args, &block)
32
+ (score,best) = (public_methods+protected_methods+private_methods).grep(/^#{name}\(/).collect {|candidate|
33
+ # extract paramters
34
+ params = /\((.*)\)/.match(candidate.to_s)[1].scan(/(\w+)-([\w:)]+)/).collect {|s,t|
35
+ [s.to_sym, t.split(/::/).inject(Kernel) {|m,c| m = m.const_get(c)}]
36
+ }
37
+ # form the list of all required argument classes
38
+ ctypes = params.values_at(*params.each_index.select {|i| params[i].first == :req}).map(&:last)
39
+
40
+ # NOTE: ruby only allows a single *args, or a list of a=1, b=2--not both together--
41
+ # only one of the following will execute
42
+
43
+ # (A) insert any optional argument classes for as many extra are present
44
+ params.each_index.select {|i| params[i].first == :opt}.each {|i|
45
+ ctypes.insert(i, params[i].last) if args.size > ctypes.size
46
+ }
47
+ # (B) insert the remaining arguments by exploding the appropriate class by the number extra
48
+ params.each_index.select {|i| params[i].first == :rest}.each {|i|
49
+ ctypes.insert(i, *([params[i].last] * (args.size - ctypes.size))) if args.size > ctypes.size
50
+ }
51
+
52
+ # now score the given args by comparing their actual classes to the predefined classes
53
+ if args.empty? and ctypes.empty?
54
+ score = 1
55
+ elsif ctypes.size == args.size
56
+ score = args.map(&:class).zip(ctypes).inject(0) {|s,t|
57
+ # apply each class comparison and require nonzero matches
58
+ s and ->(n) {s += n if n > 0}[ [ :==, :<=, ].select {|op| ->(a,&b) {b[*a]}[t, &op]}.size ]
59
+ } || 0
60
+ else
61
+ score = 0
62
+ end
63
+
64
+ [ score, candidate ]
65
+
66
+ }.max {|a,b| a[0] <=> b[0]}
67
+
68
+ (not score or score == 0) ? super : method(best)[*args, &block]
69
+ end
70
+
71
+ # The Detach::Types module is inserted as a parent of the class which includes the
72
+ # Detach mixin. This module handles inspection and aliasing of instance methods
73
+ # as they are added.
74
+ #
75
+ # Detach::Types does not need to be extended directly.
76
+ module Types
77
+ # Decorator method for defining argument signature.
78
+ #
79
+ # Example:
80
+ # taking['String']
81
+ # def foo(a)
82
+ # end
83
+ def taking
84
+ self
85
+ end
86
+ # :stopdoc:
87
+ def [](*types)
88
+ @@types = types.flatten
89
+ end
90
+ def method_added(name)
91
+ return unless @@types
92
+
93
+ # query the parameter info for the method just added
94
+ p = instance_method(name).parameters.map &:first
95
+ raise ArgumentError.new('type and parameter mismatch') unless p.size == @@types.size
96
+
97
+ # encode our defined types with parameter info into a new name and remove the original
98
+ n = (name.to_s + '(' + p.zip(@@types).collect {|p,t| "#{p}-#{t}" }.join(',') + ')').to_sym
99
+ @@types = nil
100
+
101
+ alias_method n, name unless method_defined?(n)
102
+ define_method(name) {|*args, &block| method_missing(name, *args, &block)}
103
+ end
104
+ end
105
+ end
106
+
@@ -0,0 +1,142 @@
1
+ require 'test/unit'
2
+ require 'detach'
3
+
4
+ class TestDetach < Test::Unit::TestCase
5
+
6
+ def testBasicTypes
7
+ assert_equal "String hello", Foo.new.foo('hello')
8
+ assert_equal "Integer 42", Foo.new.foo(42)
9
+ assert_equal "Strings helloworld", Foo.new.foo('hello', 'world')
10
+ assert_equal "Integers 42", Foo.new.foo(40, 2)
11
+ assert_equal "Floats 4.7", Foo.new.foo(3.1, 1.6)
12
+ assert_equal "Numbers 4.7", Foo.new.foo(3, 1.7)
13
+ end
14
+
15
+ def testNoArgs
16
+ assert_equal "Nonce", Foo.new.foo
17
+ end
18
+
19
+ def testLists
20
+ assert_equal "Object List [\"one\", 2, 3.0, \"4\"]", Foo.new.foo('one', 2, 3.0, '4')
21
+ assert_equal "String List [\"one\", \"two\", \"three\", \"four\"]", Foo.new.foo('one', 'two', 'three', 'four')
22
+
23
+ end
24
+
25
+ def testMixedLists
26
+ assert_equal "Int-String List-String 1 [] guy", Foo.new.foo(1, 'guy')
27
+ assert_equal "Int-String List-String 1 [\"is\", \"the\", \"lonliest\"] number", Foo.new.foo(1, 'is', 'the', 'lonliest', 'number')
28
+ end
29
+
30
+ def testOptional
31
+ assert_equal "Int-String-Int 7 ate 9", Foo.new.foo(7, 'ate', 9)
32
+ assert_equal "Int-String-Int 42 < 99", Foo.new.foo('<', 99)
33
+ end
34
+
35
+ def testCustomObject
36
+ assert_equal "P::M 42", Foo.new.foo(P::M.new)
37
+ end
38
+
39
+ def testConstructor
40
+ assert_equal "hello world", Wow.new("hello", "world").to_s
41
+ assert_equal "11", Wow.new(5, 6).to_s
42
+ end
43
+
44
+
45
+ #
46
+ # helper definitions
47
+ #
48
+
49
+ module P
50
+ class M
51
+ def wow
52
+ 42
53
+ end
54
+ end
55
+ end
56
+
57
+ class Foo
58
+ include Detach
59
+
60
+ taking[String, String]
61
+ def foo(a, b)
62
+ "Strings #{a + b}"
63
+ end
64
+
65
+ taking[Integer, Integer]
66
+ def foo(a, b)
67
+ "Integers #{a + b}"
68
+ end
69
+
70
+ taking[Float, Float]
71
+ def foo(a, b)
72
+ "Floats #{a + b}"
73
+ end
74
+
75
+ taking[Numeric, Numeric]
76
+ def foo(a, b)
77
+ "Numbers #{a + b}"
78
+ end
79
+
80
+ taking[String]
81
+ def foo(a)
82
+ "String #{a}"
83
+ end
84
+
85
+ taking[Integer]
86
+ def foo(a)
87
+ "Integer #{a}"
88
+ end
89
+
90
+ taking[]
91
+ def foo
92
+ "Nonce"
93
+ end
94
+
95
+ taking[Object]
96
+ def foo(*a)
97
+ "Object List #{a}"
98
+ end
99
+
100
+ taking[String]
101
+ def foo(*a)
102
+ "String List #{a}"
103
+ end
104
+
105
+ taking[Integer, String, String]
106
+ def foo(a, *b, c)
107
+ "Int-String List-String #{a} #{b} #{c}"
108
+ end
109
+
110
+ taking[Integer, String, Integer]
111
+ def foo(a=42, b, c)
112
+ "Int-String-Int #{a} #{b} #{c}"
113
+ end
114
+
115
+ taking[P::M]
116
+ def foo(m)
117
+ "P::M #{m.wow}"
118
+ end
119
+ end
120
+
121
+ class Wow
122
+ include Detach
123
+
124
+ taking[String,String]
125
+ def initialize(a,b)
126
+ @s = "#{a} #{b}"
127
+ end
128
+
129
+ taking[Integer,Integer]
130
+ def initialize(a,b)
131
+ @s = a + b
132
+ end
133
+
134
+ def to_s
135
+ @s.to_s
136
+ end
137
+ end
138
+
139
+
140
+ end
141
+
142
+
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: detach
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Calhoun
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/detach.rb
20
+ - test/test_detach.rb
21
+ homepage:
22
+ licenses: []
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.2.2
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Separate methods by argument types
44
+ test_files: []