method_args 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/README +56 -0
  2. data/Rakefile +43 -0
  3. data/extconf.rb +6 -0
  4. data/method_args.c +218 -0
  5. data/test.rb +109 -0
  6. metadata +50 -0
data/README ADDED
@@ -0,0 +1,56 @@
1
+ = Download
2
+ Type 'gem install method_args' or download manually at
3
+ http://rubyforge.org/projects/method-args
4
+
5
+ = Synopses
6
+
7
+ This extension adds args method to the Method class. Example:
8
+
9
+ require 'method_args'
10
+ class X
11
+ def hello(foo, bar)
12
+ nil
13
+ end
14
+ end
15
+ x = X.new
16
+ m = x.method(:hello)
17
+ m.args # => [:foo, :bar]
18
+
19
+
20
+ To differentiate between the different types of arguments that could be returned the library provides
21
+ Method#required_args
22
+ Method#optional_args
23
+ Method#splat_arg
24
+ For example:
25
+
26
+ class X
27
+ def hello(i, love = 2, *techno)
28
+ end
29
+ end
30
+ method = X.new.method(:hello)
31
+ method.required_args # => [:i]
32
+ method.optional_args # => [:love]
33
+ method.splat_arg # => :techno
34
+
35
+ Method#args does not support methods written in C (and never will). To test if you can call args methods on a certain method use Method#args?. Example:
36
+
37
+ X.new.args? # => true
38
+ built_in = "string".method(:split)
39
+ built_in.args? # => false
40
+ built_in.args # => raises StandardError
41
+
42
+ Method#args does not support block arguments (but might in the future).
43
+
44
+ = Acknowledgments
45
+ Most of the code here is extracted from ParseTree by Ryan Davis
46
+
47
+ = License
48
+ (The MIT License)
49
+
50
+ Copyright (c) 2007 Ryan Dahl <ry@tinyclouds.org>
51
+
52
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sub-license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
53
+
54
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
55
+
56
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+
6
+ CLEAN.add 'method_args.o'
7
+ CLOBBER.add %w{Makefile mkmf.log method_args.{bundle,so,obj,pdb,lib,def,exp}}
8
+ PKG_FILES = %w{README Rakefile extconf.rb test.rb method_args.c}
9
+
10
+ Rake::TestTask.new do |t|
11
+ t.test_files = 'test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.version = '0.0.3'
17
+ s.name = 'method_args'
18
+ s.summary = 'Provides Method#args for inspecting arguments'
19
+ s.description = s.summary
20
+ s.test_files = 'test.rb'
21
+ s.author = 'Ry Dahl'
22
+ s.email = 'ry@tinyclouds.org'
23
+ s.files = PKG_FILES
24
+ s.extensions = 'extconf.rb'
25
+ s.require_path = '.'
26
+ s.autorequire = 'rake'
27
+ s.required_ruby_version = '>= 1.8.4'
28
+ end
29
+
30
+ Rake::GemPackageTask.new(spec) do |pkg|
31
+ pkg.need_zip = true
32
+ end
33
+
34
+ task :default => [:compile, :test]
35
+
36
+ desc 'Compile the code'
37
+ task :compile => ['Makefile'] do |t|
38
+ sh 'make'
39
+ end
40
+
41
+ file 'Makefile' do |t|
42
+ ruby 'extconf.rb'
43
+ end
data/extconf.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ dir_config("method_args")
4
+ #have_library("c", "main")
5
+
6
+ create_makefile("method_args")
data/method_args.c ADDED
@@ -0,0 +1,218 @@
1
+ #include <stdio.h>
2
+ #include <ruby.h>
3
+ #include <node.h>
4
+ #include <version.h>
5
+
6
+ /* <DebugStuff> use the --debug argument to ruby to enable */
7
+ const char *node_names[] = {"METHOD", "FBODY", "CFUNC", "SCOPE", "BLOCK",
8
+ "IF", "CASE", "WHEN", "OPT_N", "WHILE", "UNTIL", "ITER", "FOR", "BREAK",
9
+ "NEXT", "REDO", "RETRY", "BEGIN", "RESCUE", "RESBODY", "ENSURE", "AND",
10
+ "OR", "NOT", "MASGN", "LASGN", "DASGN", "DASGN_CURR", "GASGN", "IASGN",
11
+ "CDECL", "CVASGN", "CVDECL", "OP_ASGN1", "OP_ASGN2", "OP_ASGN_AND",
12
+ "OP_ASGN_OR", "CALL", "FCALL", "VCALL", "SUPER", "ZSUPER", "ARRAY",
13
+ "ZARRAY", "HASH", "RETURN", "YIELD", "LVAR", "DVAR", "GVAR", "IVAR",
14
+ "CONST", "CVAR", "NTH_REF", "BACK_REF", "MATCH", "MATCH2", "MATCH3",
15
+ "LIT", "STR", "DSTR", "XSTR", "DXSTR", "EVSTR", "DREGX", "DREGX_ONCE",
16
+ "ARGS", "ARGSCAT", "ARGSPUSH", "SPLAT", "TO_ARY", "SVALUE", "BLOCK_ARG",
17
+ "BLOCK_PASS", "DEFN", "DEFS", "ALIAS", "VALIAS", "UNDEF", "CLASS", "MODULE",
18
+ "SCLASS", "COLON2", "COLON3", "CREF", "DOT2", "DOT3", "FLIP2", "FLIP3",
19
+ "ATTRSET", "SELF", "NIL", "TRUE", "FALSE", "DEFINED", "NEWLINE", "POSTEXE",
20
+ "ALLOCA", "DMETHOD", "BMETHOD", "MEMO", "IFUNC", "DSYM", "ATTRASGN", "LAST"
21
+ };
22
+
23
+ void inspect_node(NODE *n) {
24
+ if(n) {
25
+ fprintf(stderr, "node %s: %s%s%s\n",
26
+ node_names[nd_type(n)],
27
+ (RNODE(n)->u1.node != NULL ? "u1 " : " "),
28
+ (RNODE(n)->u2.node != NULL ? "u2 " : " "),
29
+ (RNODE(n)->u3.node != NULL ? "u3 " : " "));
30
+ } else {
31
+ fprintf(stderr, "null node!\n");
32
+ }
33
+ }
34
+ /* </DebugStuff> */
35
+
36
+ NODE* get_arg_node(NODE *body) {
37
+ NODE *node = NULL, *block, *args;
38
+
39
+ if(RTEST(ruby_debug)) { inspect_node(body); }
40
+ if(!body) { return NULL; }
41
+
42
+ block = body->nd_next;
43
+ if(RTEST(ruby_debug)) { inspect_node(block); }
44
+ if(!block) { return NULL; }
45
+
46
+ args = block->nd_head;
47
+ if(RTEST(ruby_debug)) { inspect_node(args); }
48
+ if(!args) { return NULL; }
49
+
50
+ return args;
51
+ }
52
+
53
+ NODE* get_body_node(VALUE method) {
54
+ #if RUBY_VERSION_CODE < 190
55
+ /* Exposing struct METHOD is terrible but allows for easily accessing body.
56
+ * We're moving to 1.9 soon so we don't need to do this in the future */
57
+ struct METHOD {
58
+ VALUE klass, rklass;
59
+ VALUE recv;
60
+ ID id, oid;
61
+ int safe_level;
62
+ NODE *body;
63
+ };
64
+ struct METHOD *data;
65
+ Data_Get_Struct(method, struct METHOD, data);
66
+ return data->body;
67
+ #else
68
+ /* 1.9 has Method#receiver. use it instead of peeking at secret structures
69
+ */
70
+ ID id;
71
+ VALUE recv;
72
+ recv = rb_funcall(method, rb_intern('receiver'), 0);
73
+ id = rb_to_id(recv);
74
+ st_lookup(RCLASS(recv)->m_tbl, id, &n);
75
+ return (NODE*)n;
76
+ #endif
77
+ }
78
+
79
+ VALUE required_args(ID* locals, int max_args) {
80
+ int i;
81
+ VALUE arg_array = rb_ary_new();
82
+ for (i = 0; i < max_args; i++) {
83
+ rb_ary_push(arg_array, ID2SYM(locals[i + 3]));
84
+ }
85
+
86
+ if(RTEST(ruby_debug)) {
87
+ fprintf(stderr, "required_args: %s\n",
88
+ RSTRING(rb_funcall(arg_array, rb_intern("inspect"), 0))->ptr);
89
+ }
90
+
91
+ return arg_array;
92
+ }
93
+
94
+ VALUE optional_args(NODE *args, ID *locals, int max_args) {
95
+ NODE *optnode;
96
+ int i;
97
+ VALUE arg_array = rb_ary_new();
98
+
99
+ i = max_args;
100
+ for(optnode = args->nd_opt; optnode; optnode = optnode->nd_next) {
101
+ rb_ary_push(arg_array, ID2SYM(locals[i + 3]));
102
+ i++;
103
+ }
104
+
105
+ if(RTEST(ruby_debug)) {
106
+ fprintf(stderr, "optional_args: %s\n",
107
+ RSTRING(rb_funcall(arg_array, rb_intern("inspect"), 0))->ptr);
108
+ }
109
+
110
+ return arg_array;
111
+ }
112
+
113
+ VALUE optional_args_length(NODE *args) {
114
+ NODE *optnode;
115
+ int l=0;
116
+
117
+ for(optnode = args->nd_opt; optnode; optnode = optnode->nd_next) { l++; }
118
+ return l;
119
+ }
120
+
121
+ /* arg_index should be optional_args.length + max_args */
122
+ VALUE splat_arg(NODE *args, ID *locals, int max_args) {
123
+ long arg_count = (long)args->nd_rest;
124
+ int arg_index = optional_args_length(args) + max_args;
125
+
126
+ if(RTEST(ruby_debug)) {
127
+ fprintf(stderr, "splat_arg: arg_count = %ld\n", arg_count);
128
+ fprintf(stderr, "splat_arg: arg_index = %d\n", arg_index);
129
+ }
130
+
131
+ /* splat argument */
132
+ if (arg_count > 0 && locals[arg_index + 3]) {
133
+ if(RTEST(ruby_debug)) { fprintf(stderr, "hello world\n"); }
134
+ return ID2SYM(locals[arg_index + 3]);
135
+ } else {
136
+ return Qnil;
137
+ }
138
+ }
139
+
140
+ VALUE rb_method_args_p(VALUE self) {
141
+ return get_arg_node(get_body_node(self)) ? Qtrue : Qfalse;
142
+ }
143
+
144
+ VALUE rb_method_args(VALUE self) {
145
+ NODE *args, *body = get_body_node(self);
146
+ ID *locals;
147
+ VALUE array, splat;
148
+ int max_args;
149
+
150
+ if(rb_method_args_p(self) == Qfalse) {
151
+ rb_raise(rb_eStandardError, "Cannot find arguments for this method");
152
+ }
153
+
154
+ args = get_arg_node(body);
155
+ locals = body->nd_tbl;
156
+ max_args = args->nd_cnt;
157
+
158
+ array = required_args(locals, max_args);
159
+ rb_ary_concat(array, optional_args(args, locals, max_args));
160
+ if((splat = splat_arg(args, locals, max_args)) != Qnil) {
161
+ rb_ary_push(array, splat);
162
+ }
163
+
164
+ return array;
165
+ }
166
+
167
+ VALUE rb_method_required_args(VALUE self) {
168
+ NODE *args, *body;
169
+
170
+ if(rb_method_args_p(self) == Qfalse) {
171
+ rb_raise(rb_eStandardError, "Cannot find arguments for this method");
172
+ }
173
+
174
+ body = get_body_node(self);
175
+ args = get_arg_node(body);
176
+
177
+ return required_args(body->nd_tbl, args->nd_cnt);
178
+ }
179
+
180
+ VALUE rb_method_optional_args(VALUE self) {
181
+ NODE *args, *body;
182
+
183
+ if(rb_method_args_p(self) == Qfalse) {
184
+ rb_raise(rb_eStandardError, "Cannot find arguments for this method");
185
+ }
186
+ body = get_body_node(self);
187
+ args = get_arg_node(body);
188
+
189
+ return optional_args(get_arg_node(body), body->nd_tbl, args->nd_cnt);
190
+ }
191
+
192
+ VALUE rb_method_splat_arg(VALUE self) {
193
+ NODE *args, *body = get_body_node(self);
194
+ ID *locals;
195
+ int max_args;
196
+
197
+ if(rb_method_args_p(self) == Qfalse) {
198
+ rb_raise(rb_eStandardError, "Cannot find arguments for this method");
199
+ }
200
+
201
+ args = get_arg_node(body);
202
+ locals = body->nd_tbl;
203
+ max_args = args->nd_cnt;
204
+
205
+ return splat_arg(args, locals, max_args);
206
+ }
207
+
208
+ void Init_method_args() {
209
+ VALUE rb_cMethod;
210
+ rb_cMethod = rb_const_get(rb_cObject, rb_intern("Method"));
211
+ rb_define_method(rb_cMethod, "args", rb_method_args, 0);
212
+ rb_define_method(rb_cMethod, "required_args", rb_method_required_args, 0);
213
+ rb_define_method(rb_cMethod, "optional_args", rb_method_optional_args, 0);
214
+ rb_define_method(rb_cMethod, "splat_arg", rb_method_splat_arg, 0);
215
+ // rb_define_method(rb_cMethod, "block_arg", rb_method_block_arg, 0);
216
+
217
+ rb_define_method(rb_cMethod, "args?", rb_method_args_p, 0);
218
+ }
data/test.rb ADDED
@@ -0,0 +1,109 @@
1
+ require 'test/unit'
2
+ require 'method_args'
3
+
4
+ class A
5
+ # Value of @@expected is [[required args], [optional args], splat arg]
6
+ @@expected = {}
7
+
8
+ @@expected[:normal] = [[:alpha, :beta], [], nil]
9
+ def normal(alpha, beta); 2; end
10
+ def self.normal(alpha, beta); 2; end
11
+
12
+
13
+ @@expected[:no_arguments] = [[], [], nil]
14
+ def no_arguments
15
+ w = 2
16
+ "hello world"
17
+ end
18
+ def self.no_arguments
19
+ w = 2
20
+ "hello world"
21
+ end
22
+
23
+ @@expected[:normal_and_optional] = [[:alpha], [:beta, :gamma], nil]
24
+ def normal_and_optional(alpha, beta = 2, gamma = 3)
25
+ 2
26
+ end
27
+ def self.normal_and_optional(alpha, beta = 2, gamma = 3)
28
+ 2
29
+ end
30
+
31
+ @@expected[:with_splat] = [[:alpha], [], :beta]
32
+ def with_splat(alpha, *beta)
33
+ 5
34
+ end
35
+ def self.with_splat(alpha, *beta)
36
+ 5
37
+ end
38
+
39
+ @@expected[:optional_w_splat] = [[], [:alpha], :beta]
40
+ def optional_w_splat(alpha=2, *beta); 5; end
41
+ def self.optional_w_splat(alpha=2, *beta); 5; end
42
+
43
+ @@expected[:only_splat] = [[], [], :alpha]
44
+ def only_splat(*alpha); 5; end
45
+ def self.only_splat(*alpha); 5; end
46
+
47
+ @@expected[:with_block] = [[:alpha], [], nil]
48
+ def with_block(alpha, &beta)
49
+ alpha
50
+ yield
51
+ 2
52
+ end
53
+ def self.with_block(alpha, &beta)
54
+ alpha
55
+ yield
56
+ 2
57
+ end
58
+ end
59
+
60
+ class MethodTest < Test::Unit::TestCase
61
+
62
+ def setup
63
+ @methods = A.class_eval "@@expected"
64
+ end
65
+
66
+ def each_method
67
+ [A.new, A].each do |object|
68
+ @methods.each_pair do |name, expected|
69
+ yield object.method(name), expected
70
+ end
71
+ end
72
+ end
73
+
74
+ def test_required_args
75
+ each_method do |method, expected|
76
+ assert_equal expected[0], method.required_args, method.inspect
77
+ end
78
+ end
79
+
80
+ def test_optional_args
81
+ each_method do |method, expected|
82
+ assert_equal expected[1], method.optional_args, method.inspect
83
+ end
84
+ end
85
+
86
+ def test_splat_arg
87
+ each_method do |method, expected|
88
+ assert_equal expected[2], method.splat_arg, method.inspect
89
+ end
90
+ end
91
+
92
+ def test_args
93
+ each_method do |method, expected|
94
+ assert_equal expected.flatten.compact, method.args, method.inspect
95
+ end
96
+ end
97
+
98
+ def test_args?
99
+ each_method do |method, expected|
100
+ assert_equal true, method.args?, method.inspect
101
+ end
102
+ end
103
+
104
+ def test_build_in_methods
105
+ m = "hello".method(:split)
106
+ assert_equal false, m.args?
107
+ assert_raises(StandardError) { m.args }
108
+ end
109
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: method_args
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.3
7
+ date: 2007-09-06 00:00:00 +02:00
8
+ summary: Provides Method#args for inspecting arguments
9
+ require_paths:
10
+ - .
11
+ email: ry@tinyclouds.org
12
+ homepage:
13
+ rubyforge_project:
14
+ description: Provides Method#args for inspecting arguments
15
+ autorequire: rake
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.4
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Ry Dahl
31
+ files:
32
+ - README
33
+ - Rakefile
34
+ - extconf.rb
35
+ - test.rb
36
+ - method_args.c
37
+ test_files:
38
+ - test.rb
39
+ rdoc_options: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ executables: []
44
+
45
+ extensions:
46
+ - extconf.rb
47
+ requirements: []
48
+
49
+ dependencies: []
50
+