Module: Boson::ArgumentInspector

Extended by:
ArgumentInspector
Included in:
ArgumentInspector
Defined in:
lib/boson/argument_inspector.rb

Overview

Extracts arguments and their default values from methods either by by scraping a method’s text or with method_added and brute force eval (thanks to eigenclass).

Constant Summary collapse

MAX_ARGS =

Max number of arguments extracted per method with scrape_with_eval

10

Instance Method Summary collapse

Instance Method Details

#format_arguments(params, values, arity, num_args) ⇒ Object

process params + values to return array of argument arrays



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/boson/argument_inspector.rb', line 32

def format_arguments(params, values, arity, num_args) #:nodoc:
  params ||= []
  params = params[0,num_args]
  params.inject([[], 0]) do |(a, i), x|
    if Array === values[i]
      [a << ["*#{x}"], i+1]
    else
      if arity < 0 && i >= arity.abs - 1
        [a << [x.to_s, values[i]], i + 1]
      else
        [a << [x.to_s], i+1]
      end
    end
  end.first
end

:nodoc:



27
28
29
# File 'lib/boson/argument_inspector.rb', line 27

def print_debug_message(klass, meth) #:nodoc:
  warn "DEBUG: Error while scraping arguments from #{klass.to_s[/\w+$/]}##{meth}: #{$!.message}"
end

#scrape_with_eval(meth, klass, object) ⇒ Object

Scrapes non-private methods for argument names and default values. Returns arguments as array of argument arrays with optional default value as a second element.

Examples:

def meth1(arg1, arg2='val', options={}) -> [['arg1'], ['arg2', 'val'], ['options', {}]]
def meth2(*args) -> [['*args']]


14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/boson/argument_inspector.rb', line 14

def scrape_with_eval(meth, klass, object)
  unless %w[initialize].include?(meth.to_s)
    return if class << object; private_instance_methods(true).map {|e| e.to_s } end.include?(meth.to_s)
  end
  params, values, arity, num_args = trace_method_args(meth, klass, object)
  return if local_variables == params # nothing new found
  format_arguments(params, values, arity, num_args)
  rescue Exception
    print_debug_message(klass, meth) if Boson.debug
  ensure
    set_trace_func(nil)
end

#trace_method_args(meth, klass, object) ⇒ Object

:nodoc:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/boson/argument_inspector.rb', line 48

def trace_method_args(meth, klass, object) #:nodoc:
  file = line = params = values = nil
  arity = klass.instance_method(meth).arity
  set_trace_func lambda{|event, file, line, id, binding, classname|
    begin
      if event[/call/] && classname == klass && id == meth
        params = eval("local_variables", binding)
        values = eval("local_variables.map{|x| eval(x.to_s)}", binding)
        throw :done
      end
    rescue Exception
      print_debug_message(klass, meth) if Boson.debug
    end
  }
  if arity >= 0
    num_args = arity
    catch(:done){ object.send(meth, *(0...arity)) }
  else
    num_args = 0
    # determine number of args (including splat & block)
    MAX_ARGS.downto(arity.abs - 1) do |i|
      catch(:done) do
        begin
          object.send(meth, *(0...i))
        rescue Exception
        end
      end
      # all nils if there's no splat and we gave too many args
      next if !values || values.compact.empty?
      k = nil
      values.each_with_index{|x,j| break (k = j) if Array === x}
      num_args = k ? k+1 : i
      break
    end
    args = (0...arity.abs-1).to_a
    catch(:done) do
      args.empty? ? object.send(meth) : object.send(meth, *args)
    end
  end
  set_trace_func(nil)
  return [params, values, arity, num_args]
end