bond 0.4.2-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|