bosonson 0.304.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.
- data/CHANGELOG.rdoc +108 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +181 -0
- data/bin/bss +6 -0
- data/bosonson.gemspec +24 -0
- data/deps.rip +2 -0
- data/lib/boson.rb +96 -0
- data/lib/boson/command.rb +196 -0
- data/lib/boson/commands.rb +7 -0
- data/lib/boson/commands/core.rb +77 -0
- data/lib/boson/commands/web_core.rb +153 -0
- data/lib/boson/index.rb +48 -0
- data/lib/boson/inspector.rb +120 -0
- data/lib/boson/inspectors/argument_inspector.rb +97 -0
- data/lib/boson/inspectors/comment_inspector.rb +100 -0
- data/lib/boson/inspectors/method_inspector.rb +98 -0
- data/lib/boson/libraries/file_library.rb +144 -0
- data/lib/boson/libraries/gem_library.rb +30 -0
- data/lib/boson/libraries/local_file_library.rb +30 -0
- data/lib/boson/libraries/module_library.rb +37 -0
- data/lib/boson/libraries/require_library.rb +23 -0
- data/lib/boson/library.rb +179 -0
- data/lib/boson/loader.rb +118 -0
- data/lib/boson/manager.rb +169 -0
- data/lib/boson/namespace.rb +31 -0
- data/lib/boson/option_command.rb +222 -0
- data/lib/boson/option_parser.rb +475 -0
- data/lib/boson/options.rb +146 -0
- data/lib/boson/pipe.rb +147 -0
- data/lib/boson/pipes.rb +75 -0
- data/lib/boson/repo.rb +107 -0
- data/lib/boson/repo_index.rb +124 -0
- data/lib/boson/runner.rb +81 -0
- data/lib/boson/runners/bin_runner.rb +208 -0
- data/lib/boson/runners/console_runner.rb +58 -0
- data/lib/boson/scientist.rb +182 -0
- data/lib/boson/util.rb +129 -0
- data/lib/boson/version.rb +3 -0
- data/lib/boson/view.rb +95 -0
- data/test/argument_inspector_test.rb +62 -0
- data/test/bin_runner_test.rb +223 -0
- data/test/command_test.rb +22 -0
- data/test/commands_test.rb +22 -0
- data/test/comment_inspector_test.rb +126 -0
- data/test/deps.rip +4 -0
- data/test/file_library_test.rb +42 -0
- data/test/loader_test.rb +235 -0
- data/test/manager_test.rb +114 -0
- data/test/method_inspector_test.rb +90 -0
- data/test/option_parser_test.rb +367 -0
- data/test/options_test.rb +189 -0
- data/test/pipes_test.rb +65 -0
- data/test/repo_index_test.rb +122 -0
- data/test/repo_test.rb +23 -0
- data/test/runner_test.rb +40 -0
- data/test/scientist_test.rb +341 -0
- data/test/test_helper.rb +130 -0
- data/test/util_test.rb +56 -0
- data/vendor/bundle/gems/bacon-bits-0.1.0/deps.rip +1 -0
- data/vendor/bundle/gems/hirb-0.6.0/test/deps.rip +4 -0
- metadata +217 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
module Boson::Commands::Core #:nodoc:
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def config
|
5
|
+
command_attributes = Boson::Command::ATTRIBUTES + [:usage, :full_name, :render_options]
|
6
|
+
library_attributes = Boson::Library::ATTRIBUTES + [:library_type]
|
7
|
+
|
8
|
+
commands = {
|
9
|
+
'render'=>{:desc=>"Render any object using Hirb"},
|
10
|
+
'menu'=>{:desc=>"Provide a menu to multi-select elements from a given array"},
|
11
|
+
'usage'=>{:desc=>"Print a command's usage", :options=>{
|
12
|
+
:verbose=>{:desc=>"Display global options", :type=>:boolean},
|
13
|
+
:render_options=>{:desc=>"Render options for option tables", :default=>{},
|
14
|
+
:keys=>[:vertical, :fields, :hide_empty]} } },
|
15
|
+
'commands'=>{
|
16
|
+
:desc=>"List or search commands. Query must come before any options.", :default_option=>'query',
|
17
|
+
:options=>{ :index=>{:type=>:boolean, :desc=>"Searches index"},
|
18
|
+
:local=>{:type=>:boolean, :desc=>"Local commands only" } },
|
19
|
+
:render_options=>{
|
20
|
+
[:headers,:H]=>{:default=>{:desc=>'description'}},
|
21
|
+
:query=>{:keys=>command_attributes, :default_keys=>'full_name'},
|
22
|
+
:fields=>{:default=>[:full_name, :lib, :alias, :usage, :desc], :values=>command_attributes, :enum=>false},
|
23
|
+
:filters=>{:default=>{:render_options=>:inspect, :options=>:inspect, :args=>:inspect, :config=>:inspect}}
|
24
|
+
}
|
25
|
+
},
|
26
|
+
'libraries'=>{
|
27
|
+
:desc=>"List or search libraries. Query must come before any options.", :default_option=>'query',
|
28
|
+
:options=>{ :index=>{:type=>:boolean, :desc=>"Searches index"},
|
29
|
+
:local=>{:type=>:boolean, :desc=>"Local libraries only" } },
|
30
|
+
:render_options=>{
|
31
|
+
:query=>{:keys=>library_attributes, :default_keys=>'name'},
|
32
|
+
:fields=>{:default=>[:name, :commands, :gems, :library_type], :values=>library_attributes, :enum=>false},
|
33
|
+
:filters=>{:default=>{:gems=>[:join, ','],:commands=>:size}, :desc=>"Filters to apply to library fields" }}
|
34
|
+
},
|
35
|
+
'load_library'=>{:desc=>"Load a library", :options=>{[:verbose,:V]=>true}}
|
36
|
+
}
|
37
|
+
|
38
|
+
{:namespace=>false, :library_file=>File.expand_path(__FILE__), :commands=>commands}
|
39
|
+
end
|
40
|
+
|
41
|
+
def commands(options={})
|
42
|
+
cmds = options[:index] ? (Boson::Index.read || true) && Boson::Index.commands : Boson.commands
|
43
|
+
options[:local] ? cmds.select {|e| e.library && e.library.local? } : cmds
|
44
|
+
end
|
45
|
+
|
46
|
+
def libraries(options={})
|
47
|
+
libs = options[:index] ? (Boson::Index.read || true) && Boson::Index.libraries : Boson.libraries
|
48
|
+
options[:local] ? libs.select {|e| e.local? } : libs
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_library(library, options={})
|
52
|
+
Boson::Manager.load(library, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def render(object, options={})
|
56
|
+
Boson::View.render(object, options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def menu(arr, options={}, &block)
|
60
|
+
Hirb::Console.format_output(arr, options.merge(:class=>"Hirb::Menu"), &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def usage(command, options={})
|
64
|
+
puts Boson::Command.usage(command)
|
65
|
+
|
66
|
+
if (cmd = Boson::Command.find(command))
|
67
|
+
if cmd.options && !cmd.options.empty?
|
68
|
+
puts "\nLOCAL OPTIONS"
|
69
|
+
cmd.option_parser.print_usage_table options[:render_options].dup.merge(:local=>true)
|
70
|
+
end
|
71
|
+
if options[:verbose] && cmd.render_option_parser
|
72
|
+
puts "\nGLOBAL OPTIONS"
|
73
|
+
cmd.render_option_parser.print_usage_table options[:render_options].dup
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module Boson::Commands::WebCore
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def config #:nodoc:
|
5
|
+
commands = {
|
6
|
+
'get'=>{ :desc=>"Gets the body of a url", :args=>[['url'],['options', {}]]},
|
7
|
+
'post'=>{ :desc=>'Posts to a url', :args=>[['url'],['options', {}]]},
|
8
|
+
'build_url'=>{ :desc=>"Builds a url, escaping the given params", :args=>[['url'],['params']]},
|
9
|
+
'browser'=>{ :desc=>"Opens urls in a browser on a Mac"},
|
10
|
+
'install'=>{ :desc=>"Installs a library by url. Library should then be loaded with load_library.",
|
11
|
+
:args=>[['url'],['options', {}]],
|
12
|
+
:options=> { :name=>{:type=>:string, :desc=>"Library name to save to"},
|
13
|
+
:force=>{:type=>:boolean, :desc=>'Overwrites an existing library'},
|
14
|
+
:default=>{:type=>:boolean, :desc=>'Adds library as a default library to main config file'},
|
15
|
+
:module_wrap=>{:type=>:boolean, :desc=>"Wraps a module around install using library name"},
|
16
|
+
:method_wrap=>{:type=>:boolean, :desc=>"Wraps a method and module around installed library using library name"}}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
{:library_file=>File.expand_path(__FILE__), :commands=>commands, :namespace=>false}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Requires libraries only once before defining method with given block
|
24
|
+
def self.def_which_requires(meth, *libs, &block)
|
25
|
+
define_method(meth) do |*args|
|
26
|
+
libs.each {|e| require e }
|
27
|
+
define_method(meth, block).call(*args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def_which_requires(:get, 'net/https') do |*args|
|
32
|
+
url, options = args[0], args[1] || {}
|
33
|
+
url = build_url(url, options[:params]) if options[:params]
|
34
|
+
Get.new(url).request(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def_which_requires(:build_url, 'cgi') do |url, params|
|
38
|
+
url + (url[/\?/] ? '&' : "?") + params.map {|k,v|
|
39
|
+
v = v.is_a?(Array) ? v.join(' ') : v.to_s
|
40
|
+
"#{k}=#{CGI.escape(v)}"
|
41
|
+
}.join("&")
|
42
|
+
end
|
43
|
+
|
44
|
+
def_which_requires(:post, 'uri', 'net/http') do |*args|
|
45
|
+
url, options = args[0], args[1] || {}
|
46
|
+
(res = Net::HTTP.post_form(URI.parse(url), options)) && res.body
|
47
|
+
end
|
48
|
+
|
49
|
+
def install(url, options={}) #:nodoc:
|
50
|
+
options[:name] ||= strip_name_from_url(url)
|
51
|
+
return puts("Please give a library name for this url.") if options[:name].empty?
|
52
|
+
filename = File.join ::Boson.repo.commands_dir, "#{options[:name]}.rb"
|
53
|
+
return puts("Library name #{options[:name]} already exists. Try a different name.") if File.exists?(filename) && !options[:force]
|
54
|
+
|
55
|
+
file_string = get(url) or raise "Unable to fetch url"
|
56
|
+
file_string = "# Originally from #{url}\n"+file_string
|
57
|
+
file_string = wrap_install(file_string, options) if options[:method_wrap] || options[:module_wrap]
|
58
|
+
|
59
|
+
File.open(filename, 'w') {|f| f.write file_string }
|
60
|
+
Boson.repo.update_config {|c| (c[:defaults] ||= []) << options[:name] } if options[:default]
|
61
|
+
puts "Saved to #{filename}."
|
62
|
+
end
|
63
|
+
|
64
|
+
# non-mac users should override this with the launchy gem
|
65
|
+
def browser(*urls)
|
66
|
+
system('open', *urls)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def wrap_install(file_string, options)
|
71
|
+
indent = " "
|
72
|
+
unless (mod_name = ::Boson::Util.camelize(options[:name]))
|
73
|
+
return puts("Can't wrap install with name #{options[:name]}")
|
74
|
+
end
|
75
|
+
|
76
|
+
file_string.gsub!(/(^)/,'\1'+indent)
|
77
|
+
file_string = "def #{options[:name]}\n#{file_string}\nend".gsub(/(^)/,'\1'+indent) if options[:method_wrap]
|
78
|
+
"module #{mod_name}\n#{file_string}\nend"
|
79
|
+
end
|
80
|
+
|
81
|
+
def strip_name_from_url(url)
|
82
|
+
url[/\/([^\/.]+)(\.[a-z]+)?$/, 1].to_s.gsub('-', '_').gsub(/[^a-zA-Z_]/, '')
|
83
|
+
end
|
84
|
+
|
85
|
+
# Used by the get command to make get requests and optionally parse json and yaml.
|
86
|
+
# Ruby 1.8.x is dependent on json gem for parsing json.
|
87
|
+
# See Get.request for options a request can take.
|
88
|
+
class Get
|
89
|
+
FORMAT_HEADERS = {
|
90
|
+
:json=>%w{application/json text/json application/javascript text/javascript},
|
91
|
+
:yaml=>%w{application/x-yaml text/yaml}
|
92
|
+
} #:nodoc:
|
93
|
+
|
94
|
+
def initialize(url, options={})
|
95
|
+
@url, @options = url, options
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the response body string or a parsed data structure. Returns nil if request fails. By default expects response
|
99
|
+
# to be 200.
|
100
|
+
# ==== Options:
|
101
|
+
# [:any_response] Returns body string for any response code. Default is false.
|
102
|
+
# [:parse] Parse the body into either json or yaml. Expects a valid format or if true autodetects one.
|
103
|
+
# Default is false.
|
104
|
+
# [:raise_error] Raises any original errors when parsing or fetching url instead of handling errors silently.
|
105
|
+
def request(options={})
|
106
|
+
@options.merge! options
|
107
|
+
body = get_body
|
108
|
+
body && @options[:parse] ? parse_body(body) : body
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
# Returns body string if successful or nil if not.
|
113
|
+
def get_body
|
114
|
+
uri = URI.parse(@url)
|
115
|
+
@response = get_response(uri)
|
116
|
+
(@options[:any_response] || @response.code == '200') ? @response.body : nil
|
117
|
+
rescue
|
118
|
+
@options[:raise_error] ? raise : puts("Error: GET '#{@url}' -> #{$!.class}: #{$!.message}")
|
119
|
+
end
|
120
|
+
|
121
|
+
def get_response(uri)
|
122
|
+
net = Net::HTTP.new(uri.host, uri.port)
|
123
|
+
net.verify_mode = OpenSSL::SSL::VERIFY_NONE if uri.scheme == 'https'
|
124
|
+
net.use_ssl = true if uri.scheme == 'https'
|
125
|
+
net.start {|http| http.request_get(uri.request_uri) }
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns nil if dependencies or parsing fails
|
129
|
+
def parse_body(body)
|
130
|
+
format = determine_format(@options[:parse])
|
131
|
+
case format
|
132
|
+
when :json
|
133
|
+
unless ::Boson::Util.safe_require 'json'
|
134
|
+
return puts("Install the json gem to parse json: sudo gem install json")
|
135
|
+
end
|
136
|
+
JSON.parse body
|
137
|
+
when :yaml
|
138
|
+
YAML::load body
|
139
|
+
else
|
140
|
+
puts "Can't parse this format."
|
141
|
+
end
|
142
|
+
rescue
|
143
|
+
@options[:raise_error] ? raise : puts("Error while parsing #{format} response of '#{@url}': #{$!.class}")
|
144
|
+
end
|
145
|
+
|
146
|
+
def determine_format(format)
|
147
|
+
return format.to_sym if %w{json yaml}.include?(format.to_s)
|
148
|
+
return :json if FORMAT_HEADERS[:json].include?(@response.content_type)
|
149
|
+
return :yaml if FORMAT_HEADERS[:yaml].include?(@response.content_type)
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/lib/boson/index.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Boson
|
2
|
+
# This class manages indexing/storing all commands and libraries. See RepoIndex for details
|
3
|
+
# about the index created for each Repo.
|
4
|
+
module Index
|
5
|
+
extend self
|
6
|
+
# Array of indexes, one per repo in Boson.repos.
|
7
|
+
def indexes
|
8
|
+
@indexes ||= Boson.repos.map {|e| RepoIndex.new(e) }
|
9
|
+
end
|
10
|
+
|
11
|
+
# Updates all repo indexes.
|
12
|
+
def update(options={})
|
13
|
+
indexes.each {|e| e.update(options) }
|
14
|
+
end
|
15
|
+
|
16
|
+
#:stopdoc:
|
17
|
+
def read
|
18
|
+
indexes.each {|e| e.read }
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_library(command, object=false)
|
22
|
+
indexes.each {|e|
|
23
|
+
(lib = e.find_library(command, object)) and return lib
|
24
|
+
}
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_command(command)
|
29
|
+
indexes.each {|e|
|
30
|
+
(cmd = Command.find(command, e.commands)) and return(cmd)
|
31
|
+
}
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def commands
|
36
|
+
indexes.map {|e| e.commands}.flatten
|
37
|
+
end
|
38
|
+
|
39
|
+
def libraries
|
40
|
+
indexes.map {|e| e.libraries}.flatten
|
41
|
+
end
|
42
|
+
|
43
|
+
def all_main_methods
|
44
|
+
indexes.map {|e| e.all_main_methods}.flatten
|
45
|
+
end
|
46
|
+
#:startdoc:
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Boson
|
2
|
+
# Scrapes and processes method attributes with the inspectors (MethodInspector, CommentInspector
|
3
|
+
# and ArgumentInspector) and hands off the data to FileLibrary objects.
|
4
|
+
#
|
5
|
+
# === Method Attributes
|
6
|
+
# Method attributes refer to (commented) Module methods placed before a command's method
|
7
|
+
# in a FileLibrary module:
|
8
|
+
# module SomeMod
|
9
|
+
# # @render_options :fields=>%w{one two}
|
10
|
+
# # @config :alias=>'so'
|
11
|
+
# options :verbose=>:boolean
|
12
|
+
# option :count, :numeric
|
13
|
+
# # Something descriptive perhaps
|
14
|
+
# def some_method(opts)
|
15
|
+
# # ...
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Method attributes serve as configuration for a method's command. All attributes should only be called once per
|
20
|
+
# method except for option. Available method attributes:
|
21
|
+
# * config: Hash to define any command attributes (see Command.new).
|
22
|
+
# * desc: String to define a command's description for a command. Defaults to first commented line above a method.
|
23
|
+
# * options: Hash to define an OptionParser object for a command's options.
|
24
|
+
# * option: Option name and value to be merged in with options. See OptionParser for what an option value can be.
|
25
|
+
# * render_options: Hash to define an OptionParser object for a command's local/global render options (see View).
|
26
|
+
#
|
27
|
+
# When deciding whether to use commented or normal Module methods, remember that commented Module methods allow
|
28
|
+
# independence from Boson (useful for testing). See CommentInspector for more about commented method attributes.
|
29
|
+
module Inspector
|
30
|
+
extend self
|
31
|
+
attr_reader :enabled
|
32
|
+
|
33
|
+
# Enable scraping by overridding method_added to snoop on a library while it's
|
34
|
+
# loading its methods.
|
35
|
+
def enable
|
36
|
+
@enabled = true
|
37
|
+
body = MethodInspector::ALL_METHODS.map {|e|
|
38
|
+
%[def #{e}(*args)
|
39
|
+
Boson::MethodInspector.#{e}(self, *args)
|
40
|
+
end]
|
41
|
+
}.join("\n") +
|
42
|
+
%[
|
43
|
+
def new_method_added(method)
|
44
|
+
Boson::MethodInspector.new_method_added(self, method)
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :_old_method_added, :method_added
|
48
|
+
alias_method :method_added, :new_method_added
|
49
|
+
]
|
50
|
+
::Module.module_eval body
|
51
|
+
end
|
52
|
+
|
53
|
+
# Disable scraping method data.
|
54
|
+
def disable
|
55
|
+
::Module.module_eval %[
|
56
|
+
Boson::MethodInspector::ALL_METHODS.each {|e| remove_method e }
|
57
|
+
alias_method :method_added, :_old_method_added
|
58
|
+
]
|
59
|
+
@enabled = false
|
60
|
+
end
|
61
|
+
|
62
|
+
# Adds method attributes scraped for the library's module to the library's commands.
|
63
|
+
def add_method_data_to_library(library)
|
64
|
+
@commands_hash = library.commands_hash
|
65
|
+
@library_file = library.library_file
|
66
|
+
MethodInspector.current_module = library.module
|
67
|
+
@store = MethodInspector.store
|
68
|
+
add_method_scraped_data
|
69
|
+
add_comment_scraped_data
|
70
|
+
end
|
71
|
+
|
72
|
+
#:stopdoc:
|
73
|
+
def add_method_scraped_data
|
74
|
+
(MethodInspector::METHODS + [:args]).each do |key|
|
75
|
+
(@store[key] || []).each do |cmd, val|
|
76
|
+
@commands_hash[cmd] ||= {}
|
77
|
+
add_valid_data_to_config(key, val, cmd)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_valid_data_to_config(key, value, cmd)
|
83
|
+
if valid_attr_value?(key, value)
|
84
|
+
add_scraped_data_to_config(key, value, cmd)
|
85
|
+
else
|
86
|
+
if Runner.debug
|
87
|
+
warn "DEBUG: Command '#{cmd}' has #{key.inspect} attribute with invalid value '#{value.inspect}'"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_scraped_data_to_config(key, value, cmd)
|
93
|
+
if value.is_a?(Hash)
|
94
|
+
if key == :config
|
95
|
+
@commands_hash[cmd] = Util.recursive_hash_merge value, @commands_hash[cmd]
|
96
|
+
else
|
97
|
+
@commands_hash[cmd][key] = Util.recursive_hash_merge value, @commands_hash[cmd][key] || {}
|
98
|
+
end
|
99
|
+
else
|
100
|
+
@commands_hash[cmd][key] ||= value
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def valid_attr_value?(key, value)
|
105
|
+
return true if (klass = MethodInspector::METHOD_CLASSES[key]).nil?
|
106
|
+
value.is_a?(klass) || value.nil?
|
107
|
+
end
|
108
|
+
|
109
|
+
def add_comment_scraped_data
|
110
|
+
(@store[:method_locations] || []).select {|k,(f,l)| f == @library_file }.each do |cmd, (file, lineno)|
|
111
|
+
scraped = CommentInspector.scrape(FileLibrary.read_library_file(file), lineno, MethodInspector.current_module)
|
112
|
+
@commands_hash[cmd] ||= {}
|
113
|
+
MethodInspector::METHODS.each do |e|
|
114
|
+
add_valid_data_to_config(e, scraped[e], cmd)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
#:startdoc:
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# Extracts arguments and their default values from methods either by
|
2
|
+
# by scraping a method's text or with method_added and brute force eval (thanks to
|
3
|
+
# {eigenclass}[http://eigenclass.org/hiki/method+arguments+via+introspection]).
|
4
|
+
module Boson::ArgumentInspector
|
5
|
+
extend self
|
6
|
+
# Returns same argument arrays as scrape_with_eval but argument defaults haven't been evaluated.
|
7
|
+
def scrape_with_text(file_string, meth)
|
8
|
+
tabspace = "[ \t]"
|
9
|
+
if match = /^#{tabspace}*def#{tabspace}+(?:\w+\.)?#{Regexp.quote(meth)}#{tabspace}*($|(?:\(|\s+)([^\n\)]+)\s*\)?\s*$)/.match(file_string)
|
10
|
+
(match.to_a[2] || '').strip.split(/\s*,\s*/).map {|e| e.split(/\s*=\s*/)}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Max number of arguments extracted per method with scrape_with_eval
|
15
|
+
MAX_ARGS = 10
|
16
|
+
# Scrapes non-private methods for argument names and default values.
|
17
|
+
# Returns arguments as array of argument arrays with optional default value as a second element.
|
18
|
+
# ====Examples:
|
19
|
+
# def meth1(arg1, arg2='val', options={}) -> [['arg1'], ['arg2', 'val'], ['options', {}]]
|
20
|
+
# def meth2(*args) -> [['*args']]
|
21
|
+
def scrape_with_eval(meth, klass, object)
|
22
|
+
unless %w[initialize].include?(meth.to_s)
|
23
|
+
return if class << object; private_instance_methods(true).map {|e| e.to_s } end.include?(meth.to_s)
|
24
|
+
end
|
25
|
+
params, values, arity, num_args = trace_method_args(meth, klass, object)
|
26
|
+
return if local_variables == params # nothing new found
|
27
|
+
format_arguments(params, values, arity, num_args)
|
28
|
+
rescue Exception
|
29
|
+
print_debug_message(klass, meth) if Boson::Runner.debug
|
30
|
+
ensure
|
31
|
+
set_trace_func(nil)
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_debug_message(klass, meth) #:nodoc:
|
35
|
+
warn "DEBUG: Error while scraping arguments from #{klass.to_s[/\w+$/]}##{meth}: #{$!.message}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# process params + values to return array of argument arrays
|
39
|
+
def format_arguments(params, values, arity, num_args) #:nodoc:
|
40
|
+
params ||= []
|
41
|
+
params = params[0,num_args]
|
42
|
+
params.inject([[], 0]) do |(a, i), x|
|
43
|
+
if Array === values[i]
|
44
|
+
[a << ["*#{x}"], i+1]
|
45
|
+
else
|
46
|
+
if arity < 0 && i >= arity.abs - 1
|
47
|
+
[a << [x.to_s, values[i]], i + 1]
|
48
|
+
else
|
49
|
+
[a << [x.to_s], i+1]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end.first
|
53
|
+
end
|
54
|
+
|
55
|
+
def trace_method_args(meth, klass, object) #:nodoc:
|
56
|
+
file = line = params = values = nil
|
57
|
+
arity = klass.instance_method(meth).arity
|
58
|
+
set_trace_func lambda{|event, file, line, id, binding, classname|
|
59
|
+
begin
|
60
|
+
if event[/call/] && classname == klass && id == meth
|
61
|
+
params = eval("local_variables", binding)
|
62
|
+
values = eval("local_variables.map{|x| eval(x.to_s)}", binding)
|
63
|
+
throw :done
|
64
|
+
end
|
65
|
+
rescue Exception
|
66
|
+
print_debug_message(klass, meth) if Boson::Runner.debug
|
67
|
+
end
|
68
|
+
}
|
69
|
+
if arity >= 0
|
70
|
+
num_args = arity
|
71
|
+
catch(:done){ object.send(meth, *(0...arity)) }
|
72
|
+
else
|
73
|
+
num_args = 0
|
74
|
+
# determine number of args (including splat & block)
|
75
|
+
MAX_ARGS.downto(arity.abs - 1) do |i|
|
76
|
+
catch(:done) do
|
77
|
+
begin
|
78
|
+
object.send(meth, *(0...i))
|
79
|
+
rescue Exception
|
80
|
+
end
|
81
|
+
end
|
82
|
+
# all nils if there's no splat and we gave too many args
|
83
|
+
next if !values || values.compact.empty?
|
84
|
+
k = nil
|
85
|
+
values.each_with_index{|x,j| break (k = j) if Array === x}
|
86
|
+
num_args = k ? k+1 : i
|
87
|
+
break
|
88
|
+
end
|
89
|
+
args = (0...arity.abs-1).to_a
|
90
|
+
catch(:done) do
|
91
|
+
args.empty? ? object.send(meth) : object.send(meth, *args)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
set_trace_func(nil)
|
95
|
+
return [params, values, arity, num_args]
|
96
|
+
end
|
97
|
+
end
|