bond 0.1.4 → 0.2.0

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