bond 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +12 -0
- data/LICENSE.txt +1 -1
- data/README.rdoc +195 -173
- data/Rakefile +9 -45
- data/lib/bond.rb +62 -93
- data/lib/bond/agent.rb +39 -33
- data/lib/bond/completion.rb +15 -27
- data/lib/bond/completions/activerecord.rb +12 -0
- data/lib/bond/completions/array.rb +1 -0
- data/lib/bond/completions/bond.rb +2 -0
- data/lib/bond/completions/hash.rb +3 -0
- data/lib/bond/completions/kernel.rb +13 -0
- data/lib/bond/completions/module.rb +7 -0
- data/lib/bond/completions/object.rb +21 -0
- data/lib/bond/completions/struct.rb +1 -0
- data/lib/bond/input.rb +28 -0
- data/lib/bond/m.rb +95 -0
- data/lib/bond/mission.rb +109 -69
- data/lib/bond/missions/anywhere_mission.rb +18 -0
- data/lib/bond/missions/default_mission.rb +16 -6
- data/lib/bond/missions/method_mission.rb +194 -13
- data/lib/bond/missions/object_mission.rb +29 -32
- data/lib/bond/missions/operator_method_mission.rb +26 -0
- data/lib/bond/rawline.rb +3 -3
- data/lib/bond/rc.rb +48 -0
- data/lib/bond/readline.rb +9 -6
- data/lib/bond/search.rb +51 -12
- data/lib/bond/version.rb +3 -0
- data/test/agent_test.rb +168 -65
- data/test/anywhere_mission_test.rb +34 -0
- data/test/bacon_extensions.rb +26 -0
- data/test/bond_test.rb +38 -23
- data/test/completion_test.rb +123 -21
- data/test/completions_test.rb +96 -0
- data/test/method_mission_test.rb +246 -0
- data/test/mission_test.rb +30 -44
- data/test/object_mission_test.rb +28 -32
- data/test/operator_method_mission_test.rb +66 -0
- data/test/search_test.rb +106 -29
- data/test/test_helper.rb +20 -13
- metadata +37 -8
- data/VERSION.yml +0 -4
- 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
|
8
|
-
# Namespace for subclasses of Bond::Mission.
|
9
|
-
class Missions; end
|
5
|
+
class FailedMissionError < StandardError; end
|
10
6
|
|
11
|
-
#
|
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
|
-
#
|
17
|
-
attr_accessor :
|
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
|
-
|
22
|
-
elsif options[:
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
39
|
-
|
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
|
43
|
-
@
|
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
|
-
|
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
|
-
#
|
52
|
-
#
|
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
|
-
|
56
|
-
|
57
|
-
@
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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 = @
|
69
|
-
|
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 =
|
79
|
-
completions = (
|
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
|
-
|
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
|
-
|
88
|
-
"
|
89
|
-
|
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
|
94
|
-
|
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
|
98
|
-
@
|
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
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
#
|
2
|
-
class Bond::
|
3
|
-
|
4
|
-
|
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
|
-
|
9
|
-
|
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
|
-
|
2
|
-
class
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
12
|
-
|
164
|
+
def current_methods
|
165
|
+
self.class.action_methods - OPERATORS
|
13
166
|
end
|
14
167
|
|
15
|
-
def
|
16
|
-
|
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
|