bond 0.1.4 → 0.2.0

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 (43) hide show
  1. data/CHANGELOG.rdoc +12 -0
  2. data/LICENSE.txt +1 -1
  3. data/README.rdoc +195 -173
  4. data/Rakefile +9 -45
  5. data/lib/bond.rb +62 -93
  6. data/lib/bond/agent.rb +39 -33
  7. data/lib/bond/completion.rb +15 -27
  8. data/lib/bond/completions/activerecord.rb +12 -0
  9. data/lib/bond/completions/array.rb +1 -0
  10. data/lib/bond/completions/bond.rb +2 -0
  11. data/lib/bond/completions/hash.rb +3 -0
  12. data/lib/bond/completions/kernel.rb +13 -0
  13. data/lib/bond/completions/module.rb +7 -0
  14. data/lib/bond/completions/object.rb +21 -0
  15. data/lib/bond/completions/struct.rb +1 -0
  16. data/lib/bond/input.rb +28 -0
  17. data/lib/bond/m.rb +95 -0
  18. data/lib/bond/mission.rb +109 -69
  19. data/lib/bond/missions/anywhere_mission.rb +18 -0
  20. data/lib/bond/missions/default_mission.rb +16 -6
  21. data/lib/bond/missions/method_mission.rb +194 -13
  22. data/lib/bond/missions/object_mission.rb +29 -32
  23. data/lib/bond/missions/operator_method_mission.rb +26 -0
  24. data/lib/bond/rawline.rb +3 -3
  25. data/lib/bond/rc.rb +48 -0
  26. data/lib/bond/readline.rb +9 -6
  27. data/lib/bond/search.rb +51 -12
  28. data/lib/bond/version.rb +3 -0
  29. data/test/agent_test.rb +168 -65
  30. data/test/anywhere_mission_test.rb +34 -0
  31. data/test/bacon_extensions.rb +26 -0
  32. data/test/bond_test.rb +38 -23
  33. data/test/completion_test.rb +123 -21
  34. data/test/completions_test.rb +96 -0
  35. data/test/method_mission_test.rb +246 -0
  36. data/test/mission_test.rb +30 -44
  37. data/test/object_mission_test.rb +28 -32
  38. data/test/operator_method_mission_test.rb +66 -0
  39. data/test/search_test.rb +106 -29
  40. data/test/test_helper.rb +20 -13
  41. metadata +37 -8
  42. data/VERSION.yml +0 -4
  43. data/lib/bond/actions.rb +0 -69
data/lib/bond/m.rb ADDED
@@ -0,0 +1,95 @@
1
+ module Bond
2
+ # Takes international quagmires (a user's completion setup) and passes them on as missions to an Agent.
3
+ module M
4
+ extend self
5
+
6
+ # See Bond.complete
7
+ def complete(options={}, &block)
8
+ if (result = agent.complete(options, &block)).is_a?(String)
9
+ $stderr.puts result
10
+ false
11
+ else
12
+ true
13
+ end
14
+ end
15
+
16
+ # See Bond.recomplete
17
+ def recomplete(options={}, &block)
18
+ if (result = agent.recomplete(options, &block)).is_a?(String)
19
+ $stderr.puts result
20
+ false
21
+ else
22
+ true
23
+ end
24
+ end
25
+
26
+ # See Bond.agent
27
+ def agent
28
+ @agent ||= Agent.new(config)
29
+ end
30
+
31
+ # See Bond.config
32
+ def config
33
+ @config ||= {:readline_plugin=>Bond::Readline, :debug=>false, :default_search=>:underscore}
34
+ end
35
+
36
+ # Resets M by deleting all missions.
37
+ def reset
38
+ MethodMission.reset
39
+ @agent = nil
40
+ end
41
+
42
+ # See Bond.spy
43
+ def spy(input)
44
+ agent.spy(input)
45
+ end
46
+
47
+ # Validates and sets values in M.config.
48
+ def debrief(options={})
49
+ config.merge! options
50
+ plugin_methods = %w{setup line_buffer}
51
+ unless config[:readline_plugin].is_a?(Module) &&
52
+ plugin_methods.all? {|e| config[:readline_plugin].instance_methods.map {|f| f.to_s}.include?(e)}
53
+ $stderr.puts "Invalid readline plugin set. Try again."
54
+ end
55
+ end
56
+
57
+ # See Bond.start
58
+ def start(options={}, &block)
59
+ debrief options
60
+ load_completions
61
+ Rc.module_eval(&block) if block
62
+ true
63
+ end
64
+
65
+ def load_completions #:nodoc:
66
+ load_file File.join(File.dirname(__FILE__), 'completion.rb')
67
+ load_file(File.join(home,'.bondrc')) if File.exists?(File.join(home, '.bondrc'))
68
+ [File.dirname(__FILE__), File.join(home, '.bond')].each do |base_dir|
69
+ load_dir(base_dir)
70
+ end
71
+ end
72
+
73
+ # Loads a completion file in Rc namespace.
74
+ def load_file(file)
75
+ Rc.module_eval File.read(file)
76
+ rescue Exception => e
77
+ $stderr.puts "Bond Error: Completion file '#{file}' failed to load with:", e.message
78
+ end
79
+
80
+ def load_dir(base_dir) #:nodoc:
81
+ if File.exists?(dir = File.join(base_dir, 'completions'))
82
+ Dir[dir + '/*.rb'].each {|file| load_file(file) }
83
+ end
84
+ end
85
+
86
+ # Find a user's home in a cross-platform way
87
+ def home
88
+ ['HOME', 'USERPROFILE'].each {|e| return ENV[e] if ENV[e] }
89
+ return "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
90
+ File.expand_path("~")
91
+ rescue
92
+ File::ALT_SEPARATOR ? "C:/" : "/"
93
+ end
94
+ end
95
+ end
data/lib/bond/mission.rb CHANGED
@@ -1,109 +1,149 @@
1
1
  module Bond
2
2
  # Occurs when a mission is incorrectly defined.
3
3
  class InvalidMissionError < StandardError; end
4
- # Occurs when a mission action is incorrectly defined.
5
- class InvalidMissionActionError < StandardError; end
6
4
  # Occurs when a mission or search action fails.
7
- class FailedExecutionError < StandardError; end
8
- # Namespace for subclasses of Bond::Mission.
9
- class Missions; end
5
+ class FailedMissionError < StandardError; end
10
6
 
11
- # A set of conditions and actions to take for a completion scenario or mission in Bond's mind.
7
+ # Represents a completion rule, given a condition (:on) on which to match and an action
8
+ # (block or :action) with which to generate possible completions.
12
9
  class Mission
13
- include Search
14
-
15
10
  class<<self
16
- # default search used across missions
17
- attr_accessor :default_search
11
+ # eval binding used across missions
12
+ attr_accessor :eval_binding
18
13
  # Handles creation of proper Mission class depending on the options passed.
19
14
  def create(options)
20
- if options[:method]
21
- Missions::MethodMission.new(options)
22
- elsif options[:object]
23
- Missions::ObjectMission.new(options)
24
- else
25
- new(options)
15
+ if options[:method] || options[:methods] then MethodMission.create(options)
16
+ elsif options[:object] then ObjectMission.new(options)
17
+ elsif options[:anywhere] then AnywhereMission.new(options)
18
+ elsif options[:all_methods] then MethodMission.new(options)
19
+ elsif options[:all_operator_methods] then OperatorMethodMission.new(options)
20
+ else new(options)
26
21
  end
27
22
  end
28
- #:stopdoc:
29
- def action_object
30
- @action_object ||= Object.new.extend(Actions)
31
- end
32
-
33
- def current_eval(string, eval_binding=nil)
34
- eval_binding ||= default_eval_binding
35
- eval(string, eval_binding)
36
- end
37
23
 
38
- def default_eval_binding
39
- Object.const_defined?(:IRB) ? IRB.CurrentContext.workspace.binding : ::TOPLEVEL_BINDING
24
+ # Calls eval with either the irb's current workspace binding or TOPLEVEL_BINDING.
25
+ def current_eval(string, ebinding=eval_binding)
26
+ eval(string, ebinding)
40
27
  end
41
28
 
42
- def default_search
43
- @default_search ||= :default
29
+ def eval_binding #:nodoc:
30
+ @eval_binding || IRB.CurrentContext.workspace.binding rescue ::TOPLEVEL_BINDING
44
31
  end
45
- #:startdoc:
46
32
  end
47
33
 
48
- attr_reader :action, :condition, :place
49
- OPERATORS = ["%", "&", "*", "**", "+", "-", "/", "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>", "[]", "[]=", "^"]
34
+ # All known operator methods
35
+ OPERATORS = %w{% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ | ~ ! != !~}
36
+ # Regular expressions which describe common objects for MethodMission and ObjectMission
37
+ OBJECTS = %w<\([^\)]*\) '[^']*' "[^"]*" \/[^\/]*\/> +
38
+ %w<(?:%q|%r|%Q|%w|%s|%)?\[[^\]]*\] (?:proc|lambda|%q|%r|%Q|%w|%s|%)?\s*\{[^\}]*\}>
50
39
 
51
- # Options are almost the same as those explained at Bond.complete. The only difference is that the action is passed
52
- # as an :action option here.
40
+ # Generates array of possible completions and searches them if search is disabled. Any values
41
+ # that aren't strings are automatically converted with to_s.
42
+ attr_reader :action
43
+ # See Bond.complete's :place.
44
+ attr_reader :place
45
+ # A MatchData object generated from matching the user input with the condition.
46
+ attr_reader :matched
47
+ # Regexp condition
48
+ attr_reader :on
49
+ # Takes same options as Bond.complete.
53
50
  def initialize(options)
54
- raise InvalidMissionError unless (options[:action] || respond_to?(:default_action)) &&
55
- (options[:on] || is_a?(Missions::DefaultMission))
56
- raise InvalidMissionError if options[:on] && !options[:on].is_a?(Regexp)
57
- @action = options[:action].is_a?(Symbol) && self.class.action_object.respond_to?(options[:action]) ?
58
- self.class.action_object.method(options[:action]) : options[:action]
59
- raise InvalidMissionActionError if @action && !@action.respond_to?(:call)
60
- @condition = options[:on]
61
- @place = options[:place]
62
- @search = options.has_key?(:search) ? options[:search] : Mission.default_search
63
- @search = method("#{@search}_search") unless @search.is_a?(Proc) || @search == false
64
- end
65
-
66
- # Returns a boolean indicating if a mission matches the given input.
51
+ raise InvalidMissionError, ":action" unless (options[:action] || respond_to?(:default_action))
52
+ raise InvalidMissionError, ":on" unless (options[:on] && options[:on].is_a?(Regexp)) || respond_to?(:default_on)
53
+ @action, @on = options[:action], options[:on]
54
+ @place = options[:place] if options[:place]
55
+ @name = options[:name] if options[:name]
56
+ @search = options.has_key?(:search) ? options[:search] : Search.default_search
57
+ end
58
+
59
+ # Returns a boolean indicating if a mission matches the given Input and should be executed for completion.
67
60
  def matches?(input)
68
- @matched = @input = @list_prefix = nil
69
- if (match = handle_valid_match(input))
70
- @input.instance_variable_set("@matched", @matched)
71
- @input.instance_eval("def self.matched; @matched ; end")
72
- end
61
+ @matched = @input = @completion_prefix = @eval_binding = nil
62
+ (match = do_match(input)) && after_match(@line = input)
73
63
  !!match
74
64
  end
75
65
 
76
66
  # Called when a mission has been chosen to autocomplete.
77
67
  def execute(input=@input)
78
- completions = @action.call(input)
79
- completions = (completions || []).map {|e| e.to_s }
80
- completions = @search.call(input || '', completions) if @search
68
+ completions = Array(call_action(input)).map {|e| e.to_s }
69
+ completions = call_search(@search, input, completions) if @search
81
70
  if @completion_prefix
82
- @completion_prefix = @completion_prefix.split(Regexp.union(*Readline::DefaultBreakCharacters.split('')))[-1]
71
+ # Everything up to last break char stays on the line.
72
+ # Must ensure only chars after break are prefixed
73
+ @completion_prefix = @completion_prefix[/([^#{Readline::DefaultBreakCharacters}]+)$/,1] || ''
83
74
  completions = completions.map {|e| @completion_prefix + e }
84
75
  end
85
76
  completions
77
+ end
78
+
79
+ # Searches possible completions from the action which match the input.
80
+ def call_search(search, input, list)
81
+ Rc.send("#{search}_search", input || '', list)
86
82
  rescue
87
- error_message = "Mission action failed to execute properly. Check your mission action with pattern #{@condition.inspect}.\n" +
88
- "Failed with error: #{$!.message}"
89
- raise FailedExecutionError, error_message
83
+ message = $!.is_a?(NoMethodError) && !Rc.respond_to?("#{search}_search") ?
84
+ "Completion search '#{search}' doesn't exist." :
85
+ "Failed during completion search with '#{$!.message}'."
86
+ raise FailedMissionError, [message, match_message]
87
+ end
88
+
89
+ # Calls the action to generate an array of possible completions.
90
+ def call_action(input)
91
+ @action.respond_to?(:call) ? @action.call(input) : Rc.send(@action, input)
92
+ rescue StandardError, SyntaxError
93
+ message = $!.is_a?(NoMethodError) && !@action.respond_to?(:call) &&
94
+ !Rc.respond_to?(@action) ? "Completion action '#{@action}' doesn't exist." :
95
+ "Failed during completion action with '#{$!.message}'."
96
+ raise FailedMissionError, [message, match_message]
97
+ end
98
+
99
+ # A message used to explains under what conditions a mission matched the user input.
100
+ # Useful for spying and debugging.
101
+ def match_message
102
+ "Matches completion with condition #{condition.inspect}."
103
+ end
104
+
105
+ # A regexp representing the condition under which a mission matches the input.
106
+ def condition
107
+ self.class.const_defined?(:CONDITION) ? Regexp.new(self.class.const_get(:CONDITION)) : @on
108
+ end
109
+
110
+ # The name or generated unique_id for a mission. Mostly for use with Bond.recomplete.
111
+ def name
112
+ @name ? @name.to_s : unique_id
113
+ end
114
+
115
+ # Method which must return non-nil for a mission to match.
116
+ def do_match(input)
117
+ @matched = input.match(@on)
118
+ end
119
+
120
+ # Stuff a mission needs to do after matching successfully, in preparation for Mission.execute.
121
+ def after_match(input)
122
+ create_input(input[/\S+$/])
90
123
  end
91
124
 
92
125
  #:stopdoc:
93
- def unique_id
94
- @condition
126
+ def condition_with_objects
127
+ self.class.const_get(:CONDITION).sub('OBJECTS', self.class.const_get(:OBJECTS).join('|'))
95
128
  end
96
129
 
97
- def set_input(input, match)
98
- @input = input[/\S+$/]
130
+ def eval_object(obj)
131
+ @evaled_object = self.class.current_eval(obj, eval_binding)
132
+ true
133
+ rescue Exception
134
+ false
99
135
  end
100
136
 
101
- def handle_valid_match(input)
102
- if (match = input.match(@condition))
103
- set_input(input, match)
104
- @matched ||= match
105
- end
106
- match
137
+ def eval_binding
138
+ @eval_binding ||= self.class.eval_binding
139
+ end
140
+
141
+ def unique_id
142
+ @on.inspect
143
+ end
144
+
145
+ def create_input(input, options={})
146
+ @input = Input.new(input, options.merge(:line=>@line, :matched=>@matched))
107
147
  end
108
148
  #:startdoc:
109
149
  end
@@ -0,0 +1,18 @@
1
+ # A mission which completes anywhere i.e. even after non word break characters such as '[' or '}'.
2
+ # It generates the following regexp condition /#{prefix}(#{anywhere})$/ and passes the first
3
+ # capture group to the mission action.
4
+ #
5
+ # ==== Bond.complete Options:
6
+ # [*:anywhere*] A regexp string which generates the first capture group in the above regexp.
7
+ # [*:prefix*] An optional string which prefixes the first capture group in the above regexp.
8
+ class Bond::AnywhereMission < Bond::Mission
9
+ def initialize(options={}) #:nodoc:
10
+ options[:on] = Regexp.new("#{options[:prefix]}(#{options[:anywhere]})$")
11
+ super
12
+ end
13
+
14
+ def after_match(input) #:nodoc:
15
+ @completion_prefix = input.to_s.sub(/#{Regexp.escape(@matched[1])}$/, '')
16
+ create_input @matched[1]
17
+ end
18
+ end
@@ -1,11 +1,21 @@
1
- # Represents a default mission which doesn't need an explicit action.
2
- class Bond::Missions::DefaultMission < Bond::Mission
3
- def initialize(options={}) #:nodoc:
4
- options[:action] ||= default_action
1
+ # This is the mission called when none of the others match.
2
+ class Bond::DefaultMission < Bond::Mission
3
+ ReservedWords = [
4
+ "BEGIN", "END", "alias", "and", "begin", "break", "case", "class", "def", "defined", "do", "else", "elsif", "end", "ensure",
5
+ "false", "for", "if", "in", "module", "next", "nil", "not", "or", "redo", "rescue", "retry", "return", "self", "super",
6
+ "then", "true", "undef", "unless", "until", "when", "while", "yield"
7
+ ]
8
+
9
+ #:stopdoc:
10
+ def initialize(options={})
11
+ options[:action] ||= method(:default)
5
12
  super
6
13
  end
14
+ def default_on; end
15
+ #:startdoc:
7
16
 
8
- def default_action #:nodoc:
9
- Object.const_defined?(:IRB) && IRB.const_defined?(:InputCompletor) ? IRB::InputCompletor::CompletionProc : :default
17
+ # Default action which generates methods, private methods, reserved words, local variables and constants.
18
+ def default(input)
19
+ Bond::Mission.current_eval("methods | private_methods | local_variables | self.class.constants") | ReservedWords
10
20
  end
11
21
  end
@@ -1,18 +1,199 @@
1
- # Represents a completion mission specified by :method in Bond.complete.
2
- class Bond::Missions::MethodMission < Bond::Mission
3
- attr_reader :method_condition
4
- def initialize(options={}) #:nodoc:
5
- @method_condition = options.delete(:method)
6
- @method_condition = Regexp.escape(@method_condition.to_s) unless @method_condition.is_a?(Regexp)
7
- options[:on] = /^\s*(#{@method_condition})\s*['"]?(.*)$/
8
- super
1
+ module Bond
2
+ # A mission which completes arguments for any module/class method that isn't an operator method.
3
+ # To create this mission or OperatorMethodMission, :method or :methods must be passed to Bond.complete.
4
+ # A completion for a given module/class effects any object that has it as an ancestor. If an object
5
+ # has two ancestors that have completions for the same method, the ancestor closer to the object is
6
+ # picked. For example, if Array#collect and Enumerable#collect have completions, argument completion on
7
+ # '[].collect ' would use Array#collect.
8
+ #--
9
+ # Unlike other missions, creating these missions with Bond.complete doesn't add more completion rules
10
+ # for an Agent to look through. Instead, all :method(s) completions are handled by one MethodMission
11
+ # object which looks them up with its own hashes. In the same way, all operator methods are
12
+ # handled by one OperatorMethodMission object.
13
+ #++
14
+ # ==== Bond.complete Options:
15
+ # [*:method*] String representing an instance (Class#method) or class method (Class.method). Gets
16
+ # its class from :class or from substring prefixing '#' or '.'. If no class is given,
17
+ # 'Kernel#' is assumed.
18
+ # [*:methods*] Array of instance and/or class methods in the format of :method.
19
+ # [*:class*] Optional string representing module/class of :method(s). Must end in '#' or '.' to
20
+ # indicate instance/class method. Suggested for use with :methods.
21
+ # [*:action*] If a string, value is assumed to be a :method and that method's action is copied.
22
+ # Otherwise defaults to normal :action behavior.
23
+ # [*:search*] If :action is a :method string, defaults to copying its search.
24
+ # Otherwise defaults to normal :search behavior.
25
+ # [*:name*, *:place*] These options aren't supported by a MethodMission/OperatorMethodMission completion.
26
+ # ==== Examples:
27
+ # Bond.complete(:methods=>%w{delete index rindex}, :class=>"Array#") {|e| e.object }
28
+ # Bond.complete(:method=>"Hash#index") {|e| e.object.values }
29
+ #
30
+ # ==== Argument Format
31
+ # All method arguments can autocomplete as symbols or strings and the first argument can be prefixed
32
+ # with '(':
33
+ # >> Bond.complete(:method=>'example') { %w{some example eh} }
34
+ # => true
35
+ # >> example '[TAB]
36
+ # eh example some
37
+ # >> example :[TAB]
38
+ # :eh :example :some
39
+ #
40
+ # >> example("[TAB]
41
+ # eh example some
42
+ #
43
+ # ==== Multiple Arguments
44
+ # Every time a comma appears after a method, Bond starts a new completion. This allows a method to
45
+ # complete multiple arguments as well as complete keys for a hash. *Each* argument can be have a unique
46
+ # set of completions since a completion action is aware of what argument it is currently completing:
47
+ # >> Bond.complete(:method=>'FileUtils.chown') {|e|
48
+ # e.argument > 3 ? %w{noop verbose} : %w{root admin me} }
49
+ # => true
50
+ # >> FileUtils.chown 'r[TAB]
51
+ # >> FileUtils.chown 'root'
52
+ # >> FileUtils.chown 'root', 'a[TAB]
53
+ # >> FileUtils.chown 'root', 'admin'
54
+ # >> FileUtils.chown 'root', 'admin', 'some_file', :v[TAB]
55
+ # >> FileUtils.chown 'root', 'admin', 'some_file', :verbose
56
+ # >> FileUtils.chown 'root', 'admin', 'some_file', :verbose=>true
57
+ class MethodMission < Bond::Mission
58
+ class<<self
59
+ # Hash of instance method completions which maps methods to hashes of modules to arrays ([action, search])
60
+ attr_accessor :actions
61
+ # Same as :actions but for class methods
62
+ attr_accessor :class_actions
63
+ # Stores last search result from MethodMission.find
64
+ attr_accessor :last_find
65
+ # Stores class from last search in MethodMission.find
66
+ attr_accessor :last_class
67
+
68
+ # Creates a method action given the same options as Bond.complete
69
+ def create(options)
70
+ if options[:action].is_a?(String)
71
+ klass, klass_meth = split_method(options[:action])
72
+ if (arr = (current_actions(options[:action])[klass_meth] || {})[klass])
73
+ options[:action], options[:search] = [arr[0], options[:search] || arr[1]]
74
+ else
75
+ raise InvalidMissionError, "string :action"
76
+ end
77
+ end
78
+
79
+ meths = options[:methods] || Array(options[:method])
80
+ raise InvalidMissionError, ":method(s)" unless meths.all? {|e| e.is_a?(String) }
81
+ if options[:class].is_a?(String)
82
+ options[:class] << '#' unless options[:class][/[#.]$/]
83
+ meths.map! {|e| options[:class] + e }
84
+ end
85
+
86
+ meths.each {|meth|
87
+ klass, klass_meth = split_method(meth)
88
+ (current_actions(meth)[klass_meth] ||= {})[klass] = [options[:action], options[:search]].compact
89
+ }
90
+ nil
91
+ end
92
+
93
+ # Resets all instance and class method actions.
94
+ def reset
95
+ @actions = {}
96
+ @class_actions = {}
97
+ end
98
+
99
+ def action_methods #:nodoc:
100
+ (actions.keys + class_actions.keys).uniq
101
+ end
102
+
103
+ # Lists all methods that have argument completions.
104
+ def all_methods
105
+ (class_actions.map {|m,h| h.map {|k,v| "#{k}.#{m}" } } +
106
+ actions.map {|m,h| h.map {|k,v| "#{k}##{m}" } }).flatten.sort
107
+ end
108
+
109
+ def current_actions(meth) #:nodoc:
110
+ meth.include?('.') ? @class_actions : @actions
111
+ end
112
+
113
+ def split_method(meth) #:nodoc:
114
+ meth = "Kernel##{meth}" if !meth.to_s[/[.#]/]
115
+ meth.split(/[.#]/,2)
116
+ end
117
+
118
+ # Returns the first completion by looking up the object's ancestors and finding the closest
119
+ # one that has a completion definition for the given method. Completion is returned
120
+ # as an array containing action proc and optional search to go with it.
121
+ def find(obj, meth)
122
+ last_find = find_with(obj, meth, :<=, @class_actions) if obj.is_a?(Module)
123
+ last_find = find_with(obj, meth, :is_a?, @actions) unless last_find
124
+ @last_class = last_find.is_a?(Array) ? last_find[0] : nil
125
+ @last_find = last_find ? last_find[1] : last_find
126
+ end
127
+
128
+ def find_with(obj, meth, find_meth, actions) #:nodoc:
129
+ (actions[meth] || {}).select {|k,v| get_class(k) }.
130
+ sort {|a,b| get_class(a[0]) <=> get_class(b[0]) || -1 }.
131
+ find {|k,v| obj.send(find_meth, get_class(k)) }
132
+ end
133
+
134
+ def get_class(klass) #:nodoc:
135
+ (@klasses ||= {})[klass] ||= any_const_get(klass)
136
+ end
137
+
138
+ # Returns a constant like Module#const_get no matter what namespace it's nested in.
139
+ # Returns nil if the constant is not found.
140
+ def any_const_get(name)
141
+ return name if name.is_a?(Module)
142
+ klass = Object
143
+ name.split('::').each {|e| klass = klass.const_get(e) }
144
+ klass
145
+ rescue
146
+ nil
147
+ end
148
+ end
149
+
150
+ self.reset
151
+ OBJECTS = %w{\S*?} + Mission::OBJECTS
152
+ CONDITION = %q{(OBJECTS)\.?(METHODS)(?:\s+|\()(['":])?(.*)$}
153
+
154
+ #:stopdoc:
155
+ def do_match(input)
156
+ (@on = default_on) && super && eval_object(@matched[1] ? @matched[1] : 'self') &&
157
+ MethodMission.find(@evaled_object, @meth = matched_method)
158
+ end
159
+
160
+ def default_on
161
+ Regexp.new condition_with_objects.sub('METHODS',Regexp.union(*current_methods).to_s)
9
162
  end
10
163
 
11
- def unique_id #:nodoc:
12
- @method_condition.is_a?(Regexp) ? @method_condition : @method_condition.to_s
164
+ def current_methods
165
+ self.class.action_methods - OPERATORS
13
166
  end
14
167
 
15
- def set_input(input, match) #:nodoc:
16
- @input = match[-1]
168
+ def default_action
169
+ MethodMission.last_find[0]
170
+ end
171
+
172
+ def matched_method
173
+ @matched[2]
174
+ end
175
+
176
+ def set_action_and_search
177
+ @action = default_action
178
+ @search = MethodMission.last_find[1] || Search.default_search
179
+ end
180
+
181
+ def after_match(input)
182
+ set_action_and_search
183
+ @completion_prefix, typed = @matched[3], @matched[-1]
184
+ input_options = {:object=>@evaled_object, :argument=>1+typed.count(','),
185
+ :arguments=>(@completion_prefix.to_s+typed).split(/\s*,\s*/) }
186
+ if typed.to_s.include?(',') && (match = typed.match(/(.*?\s*)([^,]*)$/))
187
+ typed = match[2]
188
+ typed.sub!(/^(['":])/,'')
189
+ @completion_prefix = typed.empty? ? '' : "#{@matched[3]}#{match[1]}#{$1}"
190
+ end
191
+ create_input typed, input_options
192
+ end
193
+
194
+ def match_message
195
+ "Matches completion for method '#{@meth}' in '#{MethodMission.last_class}'."
196
+ end
197
+ #:startdoc:
17
198
  end
18
- end
199
+ end