bond 0.4.2-java
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/.gemspec +28 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.rdoc +91 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +242 -0
- data/Rakefile +47 -0
- data/lib/bond.rb +127 -0
- data/lib/bond/agent.rb +108 -0
- data/lib/bond/completion.rb +16 -0
- data/lib/bond/completions/activerecord.rb +12 -0
- data/lib/bond/completions/array.rb +1 -0
- data/lib/bond/completions/bond.rb +6 -0
- data/lib/bond/completions/hash.rb +3 -0
- data/lib/bond/completions/kernel.rb +15 -0
- data/lib/bond/completions/module.rb +10 -0
- data/lib/bond/completions/object.rb +21 -0
- data/lib/bond/completions/struct.rb +1 -0
- data/lib/bond/input.rb +28 -0
- data/lib/bond/m.rb +146 -0
- data/lib/bond/mission.rb +151 -0
- data/lib/bond/missions/anywhere_mission.rb +15 -0
- data/lib/bond/missions/default_mission.rb +21 -0
- data/lib/bond/missions/method_mission.rb +197 -0
- data/lib/bond/missions/object_mission.rb +44 -0
- data/lib/bond/missions/operator_method_mission.rb +27 -0
- data/lib/bond/rc.rb +48 -0
- data/lib/bond/readline.rb +38 -0
- data/lib/bond/readlines/jruby.rb +13 -0
- data/lib/bond/readlines/rawline.rb +15 -0
- data/lib/bond/readlines/ruby.rb +9 -0
- data/lib/bond/search.rb +74 -0
- data/lib/bond/version.rb +3 -0
- data/test/agent_test.rb +235 -0
- data/test/anywhere_mission_test.rb +34 -0
- data/test/bond_test.rb +141 -0
- data/test/completion_test.rb +148 -0
- data/test/completions_test.rb +98 -0
- data/test/deps.rip +4 -0
- data/test/m_test.rb +34 -0
- data/test/method_mission_test.rb +246 -0
- data/test/mission_test.rb +51 -0
- data/test/object_mission_test.rb +59 -0
- data/test/operator_method_mission_test.rb +66 -0
- data/test/search_test.rb +140 -0
- data/test/test_helper.rb +69 -0
- metadata +167 -0
data/lib/bond/agent.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
module Bond
|
2
|
+
# Every time a completion is attempted, the Agent searches its missions for
|
3
|
+
# the first one that matches the user input. Using either the found mission
|
4
|
+
# or Agent.default_mission, the Agent executes the mission's action.
|
5
|
+
class Agent
|
6
|
+
# The array of missions that will be searched when a completion occurs.
|
7
|
+
attr_reader :missions
|
8
|
+
# An agent's best friend a.k.a. the readline plugin.
|
9
|
+
attr_reader :weapon
|
10
|
+
|
11
|
+
def initialize(options={}) #@private
|
12
|
+
setup_readline(options[:readline])
|
13
|
+
@default_mission_action = options[:default_mission] if options[:default_mission]
|
14
|
+
Mission.eval_binding = options[:eval_binding] if options[:eval_binding]
|
15
|
+
Search.default_search = options[:default_search] || :normal
|
16
|
+
@missions = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Creates a mission.
|
20
|
+
def complete(options={}, &block)
|
21
|
+
if (mission = create_mission(options, &block)).is_a?(Mission)
|
22
|
+
mission.place.is_a?(Integer) ? @missions.insert(mission.place - 1, mission).compact! : @missions << mission
|
23
|
+
sort_last_missions
|
24
|
+
end
|
25
|
+
mission
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates a mission and replaces the mission it matches if possible.
|
29
|
+
def recomplete(options={}, &block)
|
30
|
+
if (mission = create_mission(options, &block)).is_a?(Mission)
|
31
|
+
if (existing_mission = @missions.find {|e| e.name == mission.name })
|
32
|
+
@missions[@missions.index(existing_mission)] = mission
|
33
|
+
sort_last_missions
|
34
|
+
else
|
35
|
+
return "No existing mission found to recomplete."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
mission
|
39
|
+
end
|
40
|
+
|
41
|
+
# This is where the action starts when a completion is initiated. Optional
|
42
|
+
# line_buffer overrides line buffer from readline plugin.
|
43
|
+
def call(input, line_buffer=nil)
|
44
|
+
mission_input = line_buffer || @weapon.line_buffer
|
45
|
+
mission_input = $1 if mission_input !~ /#{Regexp.escape(input)}$/ && mission_input =~ /^(.*#{Regexp.escape(input)})/
|
46
|
+
(mission = find_mission(mission_input)) ? mission.execute : default_mission.execute(Input.new(input))
|
47
|
+
rescue FailedMissionError => e
|
48
|
+
completion_error(e.message, "Completion Info: #{e.mission.match_message}")
|
49
|
+
rescue
|
50
|
+
completion_error "Failed internally with '#{$!.message}'.",
|
51
|
+
"Please report this issue with debug on: Bond.config[:debug] = true."
|
52
|
+
end
|
53
|
+
|
54
|
+
# Given a hypothetical user input, reports back what mission it would have
|
55
|
+
# found and executed.
|
56
|
+
def spy(input)
|
57
|
+
if (mission = find_mission(input))
|
58
|
+
puts mission.match_message, "Possible completions: #{mission.execute.inspect}",
|
59
|
+
"Matches for #{mission.condition.inspect} are #{mission.matched.to_a.inspect}"
|
60
|
+
else
|
61
|
+
puts "Doesn't match a completion."
|
62
|
+
end
|
63
|
+
rescue FailedMissionError => e
|
64
|
+
puts e.mission.match_message, e.message,
|
65
|
+
"Matches for #{e.mission.condition.inspect} are #{e.mission.matched.to_a.inspect}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_mission(input) #@private
|
69
|
+
@missions.find {|mission| mission.matches?(input) }
|
70
|
+
end
|
71
|
+
|
72
|
+
# Default mission used by agent. An instance of DefaultMission.
|
73
|
+
def default_mission
|
74
|
+
@default_mission ||= DefaultMission.new(:action => @default_mission_action)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Resets an agent's missions
|
78
|
+
def reset
|
79
|
+
@missions = []
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
def setup_readline(plugin)
|
84
|
+
@weapon = plugin
|
85
|
+
@weapon.setup(self)
|
86
|
+
rescue
|
87
|
+
$stderr.puts "Bond Error: Failed #{plugin.to_s[/[^:]+$/]} setup with '#{$!.message}'"
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_mission(options, &block)
|
91
|
+
Mission.create options.merge!(:action => options[:action] || block)
|
92
|
+
rescue InvalidMissionError
|
93
|
+
"Invalid #{$!.message} for completion with options: #{options.inspect}"
|
94
|
+
rescue
|
95
|
+
"Unexpected error while creating completion with options #{options.inspect} and message:\n#{$!}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def sort_last_missions
|
99
|
+
@missions.replace @missions.partition {|e| e.place != :last }.flatten
|
100
|
+
end
|
101
|
+
|
102
|
+
def completion_error(desc, message)
|
103
|
+
arr = ["Bond Error: #{desc}", message]
|
104
|
+
arr << "Stack Trace: #{$!.backtrace.inspect}" if Bond.config[:debug]
|
105
|
+
arr
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# any object's methods
|
2
|
+
complete :object=>"Object"
|
3
|
+
# method arguments
|
4
|
+
complete :all_methods=>true
|
5
|
+
complete :all_operator_methods=>true
|
6
|
+
# classes and constants
|
7
|
+
complete(:name=>:constants, :anywhere=>'([A-Z][^. \(]*)::([^: .]*)') {|e|
|
8
|
+
receiver = e.matched[2]
|
9
|
+
candidates = eval("#{receiver}.constants | #{receiver}.methods") || []
|
10
|
+
normal_search(e.matched[3], candidates).map {|e| "#{receiver}::#{e}" }
|
11
|
+
}
|
12
|
+
# absolute constants
|
13
|
+
complete(:prefix=>'::', :anywhere=>'[A-Z][^:\.\(]*') {|e| Object.constants }
|
14
|
+
complete(:anywhere=>':[^:\s.]*') {|e| Symbol.all_symbols.map {|f| ":#{f}" } rescue [] }
|
15
|
+
complete(:anywhere=>'\$[^\s.]*') {|e| global_variables }
|
16
|
+
complete(:name=>:quoted_files, :on=>/[\s(]["']([^'"]*)$/, :search=>false, :place=>:last) {|e| files(e.matched[1]) }
|
@@ -0,0 +1,12 @@
|
|
1
|
+
attribute_imethods = %w{attribute_for_inspect column_for_attribute decrement} +
|
2
|
+
%w{increment update_attribute toggle update_attributes []}
|
3
|
+
complete(:methods=>attribute_imethods, :class=>"ActiveRecord::Base#") {|e| e.object.attribute_names }
|
4
|
+
|
5
|
+
attribute_cmethods = %w{attr_accessible attr_protected attr_readonly create create! decrement_counter} +
|
6
|
+
%w{destroy_all exists? increment_counter new serialize update update_all update_counters where}
|
7
|
+
complete(:methods=>attribute_cmethods, :class=>'ActiveRecord::Base.') {|e| e.object.column_names }
|
8
|
+
|
9
|
+
complete(:method=>"ActiveRecord::Base.establish_connection") { %w{adapter host username password database} }
|
10
|
+
complete(:methods=>%w{find all first last}, :class=>'ActiveRecord::Base.') {
|
11
|
+
%w{conditions order group having limit offset joins include select from readonly lock}
|
12
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
complete(:methods=>%w{delete index rindex include?}, :class=>"Array#") {|e| e.object }
|
@@ -0,0 +1,6 @@
|
|
1
|
+
complete(:methods=>%w{Bond.complete Bond.recomplete}) {
|
2
|
+
["on", "method", "methods", "class", "object", "anywhere", "prefix", "search", "action", "place", "name"]
|
3
|
+
}
|
4
|
+
complete(:methods=>['Bond.start', 'Bond.restart']) {
|
5
|
+
%w{gems readline default_mission default_search eval_binding debug eval_debug bare}
|
6
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
complete(:methods=>%w{Kernel#raise Kernel#fail}) { objects_of(Class).select {|e| e < StandardError } }
|
2
|
+
complete(:methods=>%w{Kernel#system Kernel#exec}) {|e|
|
3
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).uniq.map {|e|
|
4
|
+
File.directory?(e) ? Dir.entries(e) : []
|
5
|
+
}.flatten.uniq - ['.', '..']
|
6
|
+
}
|
7
|
+
complete(:method=>"Kernel#require", :search=>:files) {
|
8
|
+
paths = $:.map {|e| Dir["#{e}/**/*.{rb,bundle,dll,so}"].map {|f| f.sub(e+'/', '') } }.flatten
|
9
|
+
if Object.const_defined?(:Gem)
|
10
|
+
paths += Gem.path.map {|e| Dir["#{e}/gems/*/lib/*.{rb,bundle,dll,so}"].
|
11
|
+
map {|f| f.sub(/^.*\//,'') } }.flatten
|
12
|
+
end
|
13
|
+
paths.uniq
|
14
|
+
}
|
15
|
+
complete(:methods=>%w{Kernel#trace_var Kernel#untrace_var}) { global_variables.map {|e| ":#{e}"} }
|
@@ -0,0 +1,10 @@
|
|
1
|
+
complete(:methods=>%w{const_get const_set const_defined? remove_const}, :class=>"Module#") {|e| e.object.constants }
|
2
|
+
complete(:methods=>%w{class_variable_defined? class_variable_get class_variable_set remove_class_variable},
|
3
|
+
:class=>"Module#") {|e| e.object.class_variables }
|
4
|
+
complete(:methods=>%w{instance_method method_defined? module_function remove_method undef_method},
|
5
|
+
:class=>"Module#") {|e| e.object.instance_methods }
|
6
|
+
complete(:method=>"Module#public") {|e| e.object.private_instance_methods + e.object.protected_instance_methods }
|
7
|
+
complete(:method=>"Module#private") {|e| e.object.instance_methods + e.object.protected_instance_methods }
|
8
|
+
complete(:method=>"Module#protected") {|e| e.object.instance_methods + e.object.private_instance_methods }
|
9
|
+
complete(:methods=>%w{< <= <=> > >= include? include}, :class=>"Module#", :search=>:modules) { objects_of(Module) }
|
10
|
+
complete(:method=>'Module#alias_method') {|e| e.argument > 1 ? e.object.instance_methods : [] }
|
@@ -0,0 +1,21 @@
|
|
1
|
+
instance_meths = %w{instance_variable_get instance_variable_set remove_instance_variable instance_variable_defined?}
|
2
|
+
complete(:methods=>instance_meths, :class=>"Object#") {|e| e.object.instance_variables }
|
3
|
+
complete(:method=>"Object#instance_of?", :search=>:modules) { objects_of(Class) }
|
4
|
+
complete(:methods=>%w{is_a? kind_a? extend}, :class=>"Object#", :search=>:modules) { objects_of(Module) }
|
5
|
+
complete(:methods=>%w{Object#method Object#respond_to?}) {|e| e.object.methods }
|
6
|
+
complete(:method=>"Object#[]") {|e| e.object.keys rescue [] }
|
7
|
+
complete(:method=>"Object#send") {|e|
|
8
|
+
if e.argument > 1
|
9
|
+
if (meth = eval(e.arguments[0])) && meth.to_s != 'send' &&
|
10
|
+
(action = MethodMission.find(e.object, meth.to_s))
|
11
|
+
e.argument -= 1
|
12
|
+
e.arguments.shift
|
13
|
+
action[0].call(e)
|
14
|
+
end
|
15
|
+
else
|
16
|
+
send_methods(e.object)
|
17
|
+
end
|
18
|
+
}
|
19
|
+
def send_methods(obj)
|
20
|
+
(obj.methods + obj.private_methods(false)).map {|e| e.to_s } - Mission::OPERATORS
|
21
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
complete(:method=>"Struct#[]") {|e| e.object.members }
|
data/lib/bond/input.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Bond
|
2
|
+
# A string representing the last word the user has typed. This string is passed to a mission
|
3
|
+
# action to generate possible completions. This string contains a number of attributes from the
|
4
|
+
# matching mission, useful in generating completions.
|
5
|
+
class Input < String
|
6
|
+
# Actual object a user has just typed. Used by MethodMission and ObjectMission.
|
7
|
+
attr_accessor :object
|
8
|
+
# MatchData object from the matching mission (Mission#matched).
|
9
|
+
attr_reader :matched
|
10
|
+
# Current argument number and array of argument strings. Used by MethodMission.
|
11
|
+
attr_accessor :argument, :arguments
|
12
|
+
# The full line the user has typed.
|
13
|
+
attr_reader :line
|
14
|
+
def initialize(str, options={}) #@private
|
15
|
+
super(str || '')
|
16
|
+
@matched = options[:matched]
|
17
|
+
@line = options[:line]
|
18
|
+
@object = options[:object] if options[:object]
|
19
|
+
@argument = options[:argument] if options[:argument]
|
20
|
+
@arguments = options[:arguments] if options[:arguments]
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect #@private
|
24
|
+
"#<Bond::Input #{self.to_s.inspect} @matched=#{@matched.to_a.inspect} @line=#{@line.inspect} "+
|
25
|
+
"@argument=#{@argument.inspect} @arguments=#{@arguments.inspect} @object=#{@object.inspect}>"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/bond/m.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
module Bond
|
2
|
+
# Takes international quagmires (a user's completion setup) and passes them on
|
3
|
+
# as missions to an Agent.
|
4
|
+
module M
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# See {Bond#complete}
|
8
|
+
def complete(options={}, &block)
|
9
|
+
if (result = agent.complete(options, &block)).is_a?(String)
|
10
|
+
$stderr.puts "Bond Error: "+result
|
11
|
+
false
|
12
|
+
else
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# See {Bond#recomplete}
|
18
|
+
def recomplete(options={}, &block)
|
19
|
+
if (result = agent.recomplete(options, &block)).is_a?(String)
|
20
|
+
$stderr.puts "Bond Error: "+result
|
21
|
+
false
|
22
|
+
else
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# See {Bond#agent}
|
28
|
+
def agent
|
29
|
+
@agent ||= Agent.new(config)
|
30
|
+
end
|
31
|
+
|
32
|
+
# See {Bond#config}
|
33
|
+
def config
|
34
|
+
@config ||= {:debug => false, :default_search => :underscore}
|
35
|
+
end
|
36
|
+
|
37
|
+
# Resets M's missions and config
|
38
|
+
def reset
|
39
|
+
MethodMission.reset
|
40
|
+
@config = @agent = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# See {Bond#spy}
|
44
|
+
def spy(input)
|
45
|
+
agent.spy(input)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Validates and sets values in M.config.
|
49
|
+
def debrief(options={})
|
50
|
+
config.merge! options
|
51
|
+
config[:readline] ||= default_readline
|
52
|
+
if !config[:readline].is_a?(Module) &&
|
53
|
+
Bond.const_defined?(config[:readline].to_s.capitalize)
|
54
|
+
config[:readline] = Bond.const_get(config[:readline].to_s.capitalize)
|
55
|
+
end
|
56
|
+
unless %w{setup line_buffer}.all? {|e| config[:readline].respond_to?(e) }
|
57
|
+
$stderr.puts "Bond Error: Invalid readline plugin '#{config[:readline]}'."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# See {Bond#restart}
|
62
|
+
def restart(options={}, &block)
|
63
|
+
reset
|
64
|
+
start(options, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
# See {Bond#start}
|
68
|
+
def start(options={}, &block)
|
69
|
+
debrief options
|
70
|
+
@started = true
|
71
|
+
load_completions
|
72
|
+
Rc.module_eval(&block) if block
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
# See {Bond#started?}
|
77
|
+
def started?
|
78
|
+
!!@started
|
79
|
+
end
|
80
|
+
|
81
|
+
# Finds the full path to a gem's file relative it's load path directory.
|
82
|
+
# Returns nil if not found.
|
83
|
+
def find_gem_file(rubygem, file)
|
84
|
+
begin gem(rubygem); rescue Exception; end
|
85
|
+
(dir = $:.find {|e| File.exists?(File.join(e, file)) }) && File.join(dir, file)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Loads a completion file in Rc namespace.
|
89
|
+
def load_file(file)
|
90
|
+
Rc.module_eval File.read(file)
|
91
|
+
rescue Exception => e
|
92
|
+
$stderr.puts "Bond Error: Completion file '#{file}' failed to load with:", e.message
|
93
|
+
end
|
94
|
+
|
95
|
+
# Loads completion files in given directory.
|
96
|
+
def load_dir(base_dir)
|
97
|
+
if File.exists?(dir = File.join(base_dir, 'completions'))
|
98
|
+
Dir[dir + '/*.rb'].each {|file| load_file(file) }
|
99
|
+
true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Loads completions from gems
|
104
|
+
def load_gems(*gems)
|
105
|
+
gems.select {|e| load_gem_completion(e) }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Find a user's home in a cross-platform way
|
109
|
+
def home
|
110
|
+
['HOME', 'USERPROFILE'].each {|e| return ENV[e] if ENV[e] }
|
111
|
+
return "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
|
112
|
+
File.expand_path("~")
|
113
|
+
rescue
|
114
|
+
File::ALT_SEPARATOR ? "C:/" : "/"
|
115
|
+
end
|
116
|
+
|
117
|
+
protected
|
118
|
+
def default_readline
|
119
|
+
RUBY_PLATFORM[/mswin|mingw|bccwin|wince/i] ? Ruby :
|
120
|
+
RUBY_PLATFORM[/java/i] ? Jruby : Bond::Readline
|
121
|
+
end
|
122
|
+
|
123
|
+
def load_gem_completion(rubygem)
|
124
|
+
(dir = find_gem_file(rubygem, File.join(rubygem, '..', 'bond'))) ? load_dir(dir) :
|
125
|
+
rubygem[/\/|-/] ? load_plugin_file(rubygem) :
|
126
|
+
$stderr.puts("Bond Error: No completions found for gem '#{rubygem}'.")
|
127
|
+
end
|
128
|
+
|
129
|
+
def load_plugin_file(rubygem)
|
130
|
+
namespace, file = rubygem.split(/\/|-/, 2)
|
131
|
+
file += '.rb' unless file[/\.rb$/]
|
132
|
+
if (dir = $:.find {|e| File.exists?(File.join(e, namespace, 'completions', file)) })
|
133
|
+
load_file File.join(dir, namespace, 'completions', file)
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def load_completions
|
139
|
+
load_file File.join(File.dirname(__FILE__), 'completion.rb') unless config[:bare]
|
140
|
+
load_dir File.dirname(__FILE__) unless config[:bare]
|
141
|
+
load_gems *config[:gems] if config[:gems]
|
142
|
+
load_file(File.join(home,'.bondrc')) if File.exists?(File.join(home, '.bondrc')) && !config[:bare]
|
143
|
+
load_dir File.join(home, '.bond') unless config[:bare]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/bond/mission.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
module Bond
|
2
|
+
# Occurs when a mission is incorrectly defined.
|
3
|
+
class InvalidMissionError < StandardError; end
|
4
|
+
# Occurs when a mission fails.
|
5
|
+
class FailedMissionError < StandardError
|
6
|
+
# Mission that failed
|
7
|
+
attr_reader :mission
|
8
|
+
def initialize(mission); @mission = mission; end #@private
|
9
|
+
end
|
10
|
+
|
11
|
+
# Represents a completion rule, given a condition (:on) on which to match and an action
|
12
|
+
# (block or :action) with which to generate possible completions.
|
13
|
+
class Mission
|
14
|
+
class<<self
|
15
|
+
# eval binding used across missions
|
16
|
+
attr_accessor :eval_binding
|
17
|
+
# Handles creation of proper Mission class depending on the options passed.
|
18
|
+
def create(options)
|
19
|
+
if options[:method] || options[:methods] then MethodMission.create(options)
|
20
|
+
elsif options[:object] then ObjectMission.new(options)
|
21
|
+
elsif options[:anywhere] then AnywhereMission.new(options)
|
22
|
+
elsif options[:all_methods] then MethodMission.new(options)
|
23
|
+
elsif options[:all_operator_methods] then OperatorMethodMission.new(options)
|
24
|
+
else new(options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Calls eval with either the irb's current workspace binding or TOPLEVEL_BINDING.
|
29
|
+
def current_eval(string, ebinding=Mission.eval_binding)
|
30
|
+
ebinding = ebinding.call if ebinding.is_a?(Proc)
|
31
|
+
eval(string, ebinding)
|
32
|
+
end
|
33
|
+
|
34
|
+
def eval_binding #@private
|
35
|
+
@eval_binding || IRB.CurrentContext.workspace.binding rescue ::TOPLEVEL_BINDING
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# All known operator methods
|
40
|
+
OPERATORS = %w{% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ | ~ ! != !~}
|
41
|
+
# Regular expressions which describe common objects for MethodMission and ObjectMission
|
42
|
+
OBJECTS = %w<\([^\)]*\) '[^']*' "[^"]*" \/[^\/]*\/> +
|
43
|
+
%w<(?:%q|%r|%Q|%w|%s|%)?\[[^\]]*\] (?:proc|lambda|%q|%r|%Q|%w|%s|%)?\s*\{[^\}]*\}>
|
44
|
+
|
45
|
+
# Generates array of possible completions and searches them if search is disabled. Any values
|
46
|
+
# that aren't strings are automatically converted with to_s.
|
47
|
+
attr_reader :action
|
48
|
+
# See {Bond#complete}'s :place.
|
49
|
+
attr_reader :place
|
50
|
+
# A MatchData object generated from matching the user input with the condition.
|
51
|
+
attr_reader :matched
|
52
|
+
# Regexp condition
|
53
|
+
attr_reader :on
|
54
|
+
# Takes same options as {Bond#complete}.
|
55
|
+
def initialize(options)
|
56
|
+
raise InvalidMissionError, ":action" unless (options[:action] || respond_to?(:default_action))
|
57
|
+
raise InvalidMissionError, ":on" unless (options[:on] && options[:on].is_a?(Regexp)) || respond_to?(:default_on)
|
58
|
+
@action, @on = options[:action], options[:on]
|
59
|
+
@place = options[:place] if options[:place]
|
60
|
+
@name = options[:name] if options[:name]
|
61
|
+
@search = options.has_key?(:search) ? options[:search] : Search.default_search
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a boolean indicating if a mission matches the given Input and should be executed for completion.
|
65
|
+
def matches?(input)
|
66
|
+
@matched = @input = @completion_prefix = nil
|
67
|
+
(match = do_match(input)) && after_match(@line = input)
|
68
|
+
!!match
|
69
|
+
end
|
70
|
+
|
71
|
+
# Called when a mission has been chosen to autocomplete.
|
72
|
+
def execute(input=@input)
|
73
|
+
completions = Array(call_action(input)).map {|e| e.to_s }
|
74
|
+
completions = call_search(@search, input, completions) if @search
|
75
|
+
if @completion_prefix
|
76
|
+
# Everything up to last break char stays on the line.
|
77
|
+
# Must ensure only chars after break are prefixed
|
78
|
+
@completion_prefix = @completion_prefix[/([^#{Readline::DefaultBreakCharacters}]+)$/,1] || ''
|
79
|
+
completions = completions.map {|e| @completion_prefix + e }
|
80
|
+
end
|
81
|
+
completions
|
82
|
+
end
|
83
|
+
|
84
|
+
# Searches possible completions from the action which match the input.
|
85
|
+
def call_search(search, input, list)
|
86
|
+
Rc.send("#{search}_search", input || '', list)
|
87
|
+
rescue
|
88
|
+
message = $!.is_a?(NoMethodError) && !Rc.respond_to?("#{search}_search") ?
|
89
|
+
"Completion search '#{search}' doesn't exist." :
|
90
|
+
"Failed during completion search with '#{$!.message}'."
|
91
|
+
raise FailedMissionError.new(self), message
|
92
|
+
end
|
93
|
+
|
94
|
+
# Calls the action to generate an array of possible completions.
|
95
|
+
def call_action(input)
|
96
|
+
@action.respond_to?(:call) ? @action.call(input) : Rc.send(@action, input)
|
97
|
+
rescue StandardError, SyntaxError
|
98
|
+
message = $!.is_a?(NoMethodError) && !@action.respond_to?(:call) &&
|
99
|
+
!Rc.respond_to?(@action) ? "Completion action '#{@action}' doesn't exist." :
|
100
|
+
"Failed during completion action '#{name}' with '#{$!.message}'."
|
101
|
+
raise FailedMissionError.new(self), message
|
102
|
+
end
|
103
|
+
|
104
|
+
# A message used to explains under what conditions a mission matched the user input.
|
105
|
+
# Useful for spying and debugging.
|
106
|
+
def match_message
|
107
|
+
"Matches completion with condition #{condition.inspect}."
|
108
|
+
end
|
109
|
+
|
110
|
+
# A regexp representing the condition under which a mission matches the input.
|
111
|
+
def condition
|
112
|
+
self.class.const_defined?(:CONDITION) ? Regexp.new(self.class.const_get(:CONDITION)) : @on
|
113
|
+
end
|
114
|
+
|
115
|
+
# The name or generated unique_id for a mission. Mostly for use with Bond.recomplete.
|
116
|
+
def name
|
117
|
+
@name ? @name.to_s : unique_id
|
118
|
+
end
|
119
|
+
|
120
|
+
# Method which must return non-nil for a mission to match.
|
121
|
+
def do_match(input)
|
122
|
+
@matched = input.match(@on)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Stuff a mission needs to do after matching successfully, in preparation for Mission.execute.
|
126
|
+
def after_match(input)
|
127
|
+
create_input(input[/\S+$/])
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
def condition_with_objects
|
132
|
+
self.class.const_get(:CONDITION).sub('OBJECTS', self.class.const_get(:OBJECTS).join('|'))
|
133
|
+
end
|
134
|
+
|
135
|
+
def eval_object(obj)
|
136
|
+
@evaled_object = self.class.current_eval(obj)
|
137
|
+
true
|
138
|
+
rescue Exception
|
139
|
+
raise FailedMissionError.new(self), "Match failed during eval of '#{obj}'." if Bond.config[:eval_debug]
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
def unique_id
|
144
|
+
@on.inspect
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_input(input, options={})
|
148
|
+
@input = Input.new(input, options.merge(:line => @line, :matched => @matched))
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|