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.
Files changed (46) hide show
  1. data/.gemspec +28 -0
  2. data/.travis.yml +8 -0
  3. data/CHANGELOG.rdoc +91 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.rdoc +242 -0
  6. data/Rakefile +47 -0
  7. data/lib/bond.rb +127 -0
  8. data/lib/bond/agent.rb +108 -0
  9. data/lib/bond/completion.rb +16 -0
  10. data/lib/bond/completions/activerecord.rb +12 -0
  11. data/lib/bond/completions/array.rb +1 -0
  12. data/lib/bond/completions/bond.rb +6 -0
  13. data/lib/bond/completions/hash.rb +3 -0
  14. data/lib/bond/completions/kernel.rb +15 -0
  15. data/lib/bond/completions/module.rb +10 -0
  16. data/lib/bond/completions/object.rb +21 -0
  17. data/lib/bond/completions/struct.rb +1 -0
  18. data/lib/bond/input.rb +28 -0
  19. data/lib/bond/m.rb +146 -0
  20. data/lib/bond/mission.rb +151 -0
  21. data/lib/bond/missions/anywhere_mission.rb +15 -0
  22. data/lib/bond/missions/default_mission.rb +21 -0
  23. data/lib/bond/missions/method_mission.rb +197 -0
  24. data/lib/bond/missions/object_mission.rb +44 -0
  25. data/lib/bond/missions/operator_method_mission.rb +27 -0
  26. data/lib/bond/rc.rb +48 -0
  27. data/lib/bond/readline.rb +38 -0
  28. data/lib/bond/readlines/jruby.rb +13 -0
  29. data/lib/bond/readlines/rawline.rb +15 -0
  30. data/lib/bond/readlines/ruby.rb +9 -0
  31. data/lib/bond/search.rb +74 -0
  32. data/lib/bond/version.rb +3 -0
  33. data/test/agent_test.rb +235 -0
  34. data/test/anywhere_mission_test.rb +34 -0
  35. data/test/bond_test.rb +141 -0
  36. data/test/completion_test.rb +148 -0
  37. data/test/completions_test.rb +98 -0
  38. data/test/deps.rip +4 -0
  39. data/test/m_test.rb +34 -0
  40. data/test/method_mission_test.rb +246 -0
  41. data/test/mission_test.rb +51 -0
  42. data/test/object_mission_test.rb +59 -0
  43. data/test/operator_method_mission_test.rb +66 -0
  44. data/test/search_test.rb +140 -0
  45. data/test/test_helper.rb +69 -0
  46. metadata +167 -0
@@ -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,3 @@
1
+ complete(:methods=>%w{delete fetch store, [] has_key? key? include? member? values_at},
2
+ :class=>"Hash#") {|e| e.object.keys }
3
+ complete(:methods=>%w{index value? has_value?}, :class=>"Hash#") {|e| e.object.values }
@@ -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 }
@@ -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
@@ -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
@@ -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