bond 0.4.2-java

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