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.
- 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
|