cldwalker-bond 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +11 -0
- data/README.rdoc +12 -7
- data/VERSION.yml +1 -1
- data/lib/bond/actions.rb +65 -0
- data/lib/bond/agent.rb +12 -3
- data/lib/bond/completion.rb +28 -0
- data/lib/bond/mission.rb +48 -19
- data/lib/bond/missions/default_mission.rb +1 -1
- data/lib/bond/missions/method_mission.rb +1 -1
- data/lib/bond/missions/object_mission.rb +18 -20
- data/lib/bond/search.rb +9 -4
- data/lib/bond.rb +19 -3
- data/test/agent_test.rb +27 -29
- data/test/bond_test.rb +36 -4
- data/test/completion_test.rb +38 -0
- data/test/mission_test.rb +31 -20
- data/test/object_mission_test.rb +34 -14
- data/test/search_test.rb +29 -16
- data/test/test_helper.rb +6 -3
- metadata +6 -2
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
== 0.1.3
|
2
|
+
* Fixing deployment mistake
|
3
|
+
|
4
|
+
== 0.1.2
|
5
|
+
* Added bond/completion which is a drop-in enhancement of irb/completion
|
6
|
+
* Added ability to define predefined actions in Bond::Actions
|
7
|
+
* Fixed underscore search bug
|
8
|
+
* Fixed word breaking completion bug
|
9
|
+
* Fixed irb's completion inconsistencies
|
10
|
+
* Added ability to specify :default_search for Bond.debrief
|
11
|
+
* Added placement of completions with :place for Bond.complete
|
1
12
|
== 0.1.1
|
2
13
|
* Added Bond.spy to debug completions
|
3
14
|
* Fixed object completion failing in irbrc
|
data/README.rdoc
CHANGED
@@ -15,6 +15,15 @@ Install the gem with:
|
|
15
15
|
|
16
16
|
== Usage
|
17
17
|
|
18
|
+
To start of with, you may want to replace irb's completion (irb/completion) with Bond's enhanced version
|
19
|
+
in your irbrc :
|
20
|
+
|
21
|
+
require 'bond'
|
22
|
+
require 'bond/completion'
|
23
|
+
|
24
|
+
This should give you more consistent method completion on any objects, file completion of strings
|
25
|
+
and argument completion of Kernel#require, Kernel#system and the backtick (`).
|
26
|
+
|
18
27
|
=== Argument Completion for Methods
|
19
28
|
|
20
29
|
bash> irb -rirb/completion -rubygems
|
@@ -179,12 +188,8 @@ which Bond uses to read Readline's full buffer. Thanks also goes out to Takao Ko
|
|
179
188
|
commiting}[http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/ext/readline/readline.c?view=diff&r1=24018&r2=24019]
|
180
189
|
this Readline enhancement to ruby.
|
181
190
|
|
182
|
-
==
|
183
|
-
|
184
|
-
character typed.
|
191
|
+
== Links
|
192
|
+
* http://tagaholic.me/2009/07/16/bond-from-irb-with-completion-love.html
|
185
193
|
|
186
194
|
== Todo
|
187
|
-
* Allow
|
188
|
-
* Provide a set of Bond completions which can serve as a drop-in replacement for irb/completion.
|
189
|
-
* Argument autocompletion by object class.
|
190
|
-
* Make replacement of existing completions not require doing a Bond.reset.
|
195
|
+
* Allow usage of prefined Bond::Actions in action procs.
|
data/VERSION.yml
CHANGED
data/lib/bond/actions.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Bond
|
2
|
+
# Namespace for mission actions.
|
3
|
+
module Actions
|
4
|
+
ReservedWords = [
|
5
|
+
"BEGIN", "END", "alias", "and", "begin", "break", "case", "class", "def", "defined", "do", "else", "elsif", "end", "ensure",
|
6
|
+
"false", "for", "if", "in", "module", "next", "nil", "not", "or", "redo", "rescue", "retry", "return", "self", "super",
|
7
|
+
"then", "true", "undef", "unless", "until", "when", "while", "yield"
|
8
|
+
]
|
9
|
+
|
10
|
+
# Helper function for evaluating strings in the current console binding.
|
11
|
+
def current_eval(string)
|
12
|
+
Missions::ObjectMission.current_eval(string)
|
13
|
+
rescue Exception
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Completes backtick and Kernel#system with shell commands available in ENV['PATH']
|
18
|
+
def shell_commands(input)
|
19
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).uniq.map {|e| Dir.entries(e) }.flatten.uniq - ['.', '..']
|
20
|
+
end
|
21
|
+
|
22
|
+
# Default completion for non-irb console and bond/completion
|
23
|
+
def default(input)
|
24
|
+
current_eval("methods | private_methods | local_variables | self.class.constants") | ReservedWords
|
25
|
+
end
|
26
|
+
|
27
|
+
# File completion
|
28
|
+
def files(input)
|
29
|
+
(::Readline::FILENAME_COMPLETION_PROC.call(input) || []).map {|f|
|
30
|
+
f =~ /^~/ ? File.expand_path(f) : f
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def quoted_files(input) #:nodoc:
|
35
|
+
files(input.matched[1])
|
36
|
+
end
|
37
|
+
|
38
|
+
def constants(input) #:nodoc:
|
39
|
+
receiver = input.matched[1]
|
40
|
+
candidates = current_eval("#{receiver}.constants | #{receiver}.methods")
|
41
|
+
candidates.grep(/^#{Regexp.escape(input.matched[4])}/).map {|e| receiver + "::" + e}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Completes Kernel#require
|
45
|
+
def method_require(input)
|
46
|
+
fs = ::File::SEPARATOR
|
47
|
+
extensions_regex = /((\.(so|dll|rb|bundle))|#{fs})$/i
|
48
|
+
input =~ /^(\.{0,2}#{fs}|~)/ and return files(input).select {|f| f =~ extensions_regex or File.directory? f }
|
49
|
+
dir_entries = proc {|dir| Dir.entries(dir).delete_if {|e| %w{. ..}.include?(e) }.map {|f|
|
50
|
+
File.directory?(File.join(dir,f)) ? f+fs : f } }
|
51
|
+
input_regex = /^#{Regexp.escape(input)}/
|
52
|
+
|
53
|
+
$:.select {|e| File.directory?(e)}.inject([]) do |t,dir|
|
54
|
+
if input[/.$/] == fs && File.directory?(File.join(dir,input))
|
55
|
+
matches = dir_entries.call(File.join(dir,input)).select {|e| e =~ extensions_regex }.map {|e| input + e }
|
56
|
+
else
|
57
|
+
entries = input.include?(fs) && File.directory?(File.join(dir,File.dirname(input))) ?
|
58
|
+
dir_entries.call(File.join(dir,File.dirname(input))).map {|e| File.join(File.dirname(input), e) } : dir_entries.call(dir)
|
59
|
+
matches = entries.select {|e| e=~ extensions_regex && e =~ input_regex }
|
60
|
+
end
|
61
|
+
t += matches
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/bond/agent.rb
CHANGED
@@ -9,18 +9,27 @@ module Bond
|
|
9
9
|
extend(options[:readline_plugin])
|
10
10
|
@default_mission_action = options[:default_mission] if options[:default_mission]
|
11
11
|
@eval_binding = options[:eval_binding] if options[:eval_binding]
|
12
|
+
Mission.default_search = options[:default_search] if options[:default_search]
|
12
13
|
setup
|
13
14
|
@missions = []
|
14
15
|
end
|
15
16
|
|
16
17
|
def complete(options={}, &block) #:nodoc:
|
17
|
-
|
18
|
+
options[:action] ||= block
|
19
|
+
mission = Mission.create(options.merge(:eval_binding=>@eval_binding))
|
20
|
+
mission.place.is_a?(Integer) ? @missions.insert(mission.place - 1, mission).compact! : @missions << mission
|
21
|
+
@missions.replace @missions.partition {|e| e.place != :last }.flatten
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset #:nodoc:
|
25
|
+
@missions = []
|
18
26
|
end
|
19
27
|
|
20
28
|
# This is where the action starts when a completion is initiated.
|
21
29
|
def call(input)
|
22
|
-
|
23
|
-
|
30
|
+
mission_input = line_buffer
|
31
|
+
mission_input = $1 if mission_input !~ /#{Regexp.escape(input)}$/ && mission_input =~ /^(.*#{Regexp.escape(input)})/
|
32
|
+
(mission = find_mission(mission_input)) ? mission.execute : default_mission.execute(input)
|
24
33
|
rescue FailedExecutionError
|
25
34
|
$stderr.puts "", $!.message
|
26
35
|
rescue
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# You shouldn't place Bond.complete statements before requiring this file
|
2
|
+
# unless you're also reproducing this Bond.debrief
|
3
|
+
Bond.debrief(:default_search=>:underscore) unless Bond.config[:default_search]
|
4
|
+
Bond.debrief(:default_mission=>:default) unless Bond.config[:default_mission]
|
5
|
+
Bond.complete(:method=>/system|`/, :action=>:shell_commands)
|
6
|
+
Bond.complete(:method=>'require', :action=>:method_require, :search=>false)
|
7
|
+
|
8
|
+
# irb/completion reproduced without the completion quirks
|
9
|
+
# Completes classes and constants
|
10
|
+
Bond.complete(:on=>/(((::)?[A-Z][^:.\(]*)+)::?([^:.]*)$/, :action=>:constants, :search=>false)
|
11
|
+
# Completes absolute constants
|
12
|
+
Bond.complete(:on=>/::([A-Z][^:\.\(]*)$/, :search=>false) {|e|
|
13
|
+
Object.constants.grep(/^#{Regexp.escape(e.matched[1])}/).collect{|f| "::" + f}
|
14
|
+
}
|
15
|
+
# Completes symbols
|
16
|
+
Bond.complete(:on=>/(:[^:\s.]*)$/) {|e|
|
17
|
+
Symbol.respond_to?(:all_symbols) ? Symbol.all_symbols.map {|f| ":#{f}" } : []
|
18
|
+
}
|
19
|
+
# Completes global variables
|
20
|
+
Bond.complete(:on=>/(\$[^\s.]*)$/, :search=>false) {|e|
|
21
|
+
global_variables.grep(/^#{Regexp.escape(e.matched[1])}/)
|
22
|
+
}
|
23
|
+
# Completes files
|
24
|
+
Bond.complete(:on=>/\s+["']([^'"]*)$/, :search=>false, :action=>:quoted_files, :place=>:last)
|
25
|
+
# Completes any object's methods
|
26
|
+
Bond.complete(:object=>"Object", :place=>:last)
|
27
|
+
# Completes method completion anywhere in the line
|
28
|
+
Bond.complete(:on=>/([^.\s]+)\.([^.\s]*)$/, :object=>"Object", :place=>:last)
|
data/lib/bond/mission.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
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
|
4
6
|
# Occurs when a mission or search action fails.
|
5
7
|
class FailedExecutionError < StandardError; end
|
6
8
|
# Namespace for subclasses of Bond::Mission.
|
@@ -10,18 +12,40 @@ module Bond
|
|
10
12
|
class Mission
|
11
13
|
include Search
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
class<<self
|
16
|
+
# default search used across missions
|
17
|
+
attr_accessor :default_search
|
18
|
+
# Handles creation of proper Mission class depending on the options passed.
|
19
|
+
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)
|
26
|
+
end
|
21
27
|
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
|
+
|
38
|
+
def default_eval_binding
|
39
|
+
Object.const_defined?(:IRB) ? IRB.CurrentContext.workspace.binding : ::TOPLEVEL_BINDING
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_search
|
43
|
+
@default_search ||= :default
|
44
|
+
end
|
45
|
+
#:startdoc:
|
22
46
|
end
|
23
47
|
|
24
|
-
attr_reader :action, :condition
|
48
|
+
attr_reader :action, :condition, :place
|
25
49
|
OPERATORS = ["%", "&", "*", "**", "+", "-", "/", "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>", "[]", "[]=", "^"]
|
26
50
|
|
27
51
|
# Options are almost the same as those explained at Bond.complete. The only difference is that the action is passed
|
@@ -30,14 +54,18 @@ module Bond
|
|
30
54
|
raise InvalidMissionError unless (options[:action] || respond_to?(:default_action)) &&
|
31
55
|
(options[:on] || is_a?(Missions::DefaultMission))
|
32
56
|
raise InvalidMissionError if options[:on] && !options[:on].is_a?(Regexp)
|
33
|
-
@action = options[:action]
|
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)
|
34
60
|
@condition = options[:on]
|
35
|
-
@
|
36
|
-
@search =
|
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
|
37
64
|
end
|
38
65
|
|
39
66
|
# Returns a boolean indicating if a mission matches the given input.
|
40
67
|
def matches?(input)
|
68
|
+
@matched = @input = @list_prefix = nil
|
41
69
|
if (match = handle_valid_match(input))
|
42
70
|
@input.instance_variable_set("@matched", @matched)
|
43
71
|
@input.instance_eval("def self.matched; @matched ; end")
|
@@ -46,14 +74,15 @@ module Bond
|
|
46
74
|
end
|
47
75
|
|
48
76
|
# Called when a mission has been chosen to autocomplete.
|
49
|
-
def execute(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
@
|
77
|
+
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
|
81
|
+
if @completion_prefix
|
82
|
+
@completion_prefix = @completion_prefix.split(Regexp.union(*Readline::DefaultBreakCharacters.split('')))[-1]
|
83
|
+
completions = completions.map {|e| @completion_prefix + e }
|
56
84
|
end
|
85
|
+
completions
|
57
86
|
rescue
|
58
87
|
error_message = "Mission action failed to execute properly. Check your mission action with pattern #{@condition.inspect}.\n" +
|
59
88
|
"Failed with error: #{$!.message}"
|
@@ -6,6 +6,6 @@ class Bond::Missions::DefaultMission < Bond::Mission
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def default_action #:nodoc:
|
9
|
-
Object.const_defined?(:IRB) && IRB.const_defined?(:InputCompletor) ? IRB::InputCompletor::CompletionProc :
|
9
|
+
Object.const_defined?(:IRB) && IRB.const_defined?(:InputCompletor) ? IRB::InputCompletor::CompletionProc : :default
|
10
10
|
end
|
11
11
|
end
|
@@ -3,7 +3,7 @@ class Bond::Missions::MethodMission < Bond::Mission
|
|
3
3
|
attr_reader :method_condition
|
4
4
|
def initialize(options={}) #:nodoc:
|
5
5
|
@method_condition = options.delete(:method)
|
6
|
-
@method_condition = Regexp.
|
6
|
+
@method_condition = Regexp.escape(@method_condition.to_s) unless @method_condition.is_a?(Regexp)
|
7
7
|
options[:on] = /^\s*(#{@method_condition})\s*['"]?(.*)$/
|
8
8
|
super
|
9
9
|
end
|
@@ -7,41 +7,39 @@ class Bond::Missions::ObjectMission < Bond::Mission
|
|
7
7
|
|
8
8
|
def initialize(options={})
|
9
9
|
@object_condition = options.delete(:object)
|
10
|
-
@object_condition = /^#{Regexp.
|
11
|
-
options[:on]
|
10
|
+
@object_condition = /^#{Regexp.escape(@object_condition.to_s)}$/ unless @object_condition.is_a?(Regexp)
|
11
|
+
options[:on] ||= /(\S+|[^.]+)\.([^.\s]*)$/
|
12
12
|
@eval_binding = options[:eval_binding]
|
13
13
|
super
|
14
14
|
end
|
15
15
|
|
16
16
|
def handle_valid_match(input)
|
17
|
-
match = super
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
if (match = super)
|
18
|
+
begin
|
19
|
+
eval_object(match)
|
20
|
+
rescue Exception
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
if (match = @evaled_object.class.ancestors.any? {|e| e.to_s =~ @object_condition })
|
24
|
+
@completion_prefix = @matched[1] + "."
|
25
|
+
@input = @matched[2]
|
26
|
+
@input.instance_variable_set("@object", @evaled_object)
|
27
|
+
@input.instance_eval("def self.object; @object ; end")
|
28
|
+
@action ||= lambda {|e| default_action(e.object) }
|
29
|
+
else
|
30
|
+
match = false
|
31
|
+
end
|
26
32
|
end
|
27
33
|
match
|
28
34
|
end
|
29
35
|
|
30
36
|
def eval_object(match)
|
31
37
|
@matched = match
|
32
|
-
@evaled_object =
|
38
|
+
@evaled_object = self.class.current_eval(match[1], @eval_binding)
|
33
39
|
end
|
34
40
|
|
35
41
|
def default_action(obj)
|
36
42
|
obj.methods.map {|e| e.to_s} - OPERATORS
|
37
43
|
end
|
38
|
-
|
39
|
-
def eval_binding
|
40
|
-
@eval_binding ||= default_eval_binding
|
41
|
-
end
|
42
|
-
|
43
|
-
def default_eval_binding
|
44
|
-
Object.const_defined?(:IRB) ? IRB.CurrentContext.workspace.binding : ::TOPLEVEL_BINDING
|
45
|
-
end
|
46
44
|
#:startdoc:
|
47
45
|
end
|
data/lib/bond/search.rb
CHANGED
@@ -21,10 +21,15 @@ module Bond
|
|
21
21
|
# at the beginning of an underscored word. For example, to choose the first completion between 'so_long' and 'so_larger',
|
22
22
|
# type 's-lo'.
|
23
23
|
def underscore_search(input, list)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
if input.include?("-")
|
25
|
+
index = 0
|
26
|
+
input.split('-').inject(list) {|new_list,e|
|
27
|
+
new_list = new_list.select {|f| f.split(/_+/)[index] =~ /^#{Regexp.escape(e)}/ };
|
28
|
+
index +=1; new_list
|
29
|
+
}
|
30
|
+
else
|
31
|
+
default_search(input, list)
|
32
|
+
end
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
data/lib/bond.rb
CHANGED
@@ -4,6 +4,7 @@ require 'bond/readline'
|
|
4
4
|
require 'bond/rawline'
|
5
5
|
require 'bond/agent'
|
6
6
|
require 'bond/search'
|
7
|
+
require 'bond/actions'
|
7
8
|
require 'bond/mission'
|
8
9
|
require 'bond/missions/default_mission'
|
9
10
|
require 'bond/missions/method_mission'
|
@@ -16,8 +17,8 @@ require 'bond/missions/object_mission'
|
|
16
17
|
# * Bond can work outside of irb and readline when debriefed with Bond.debrief. This should be called before any Bond.complete calls.
|
17
18
|
# * Bond doesn't take over completion until an explicit Bond.complete is called.
|
18
19
|
# * Order of completion missions matters. The order they're defined in is the order Bond searches
|
19
|
-
# when looking for a matching completion. This means that
|
20
|
-
#
|
20
|
+
# when looking for a matching completion. This means that more specific completions like method and object completions should come
|
21
|
+
# before more general ones. You can tweak completion placement by passing :place to Bond.complete.
|
21
22
|
# * If no completion missions match, then Bond falls back on a default mission. If using irb and irb/completion
|
22
23
|
# this falls back on irb's completion. Otherwise an empty completion list is returned.
|
23
24
|
module Bond
|
@@ -41,11 +42,15 @@ module Bond
|
|
41
42
|
# traditional searching i.e. looking at the beginning of a string for possible matches. If false, search is turned off and
|
42
43
|
# assumed to be done in the action block. Possible symbols are :anywhere, :ignore_case and :underscore. See Bond::Search for
|
43
44
|
# more info about them. A proc is given two arguments: the input string and an array of possible completions.
|
45
|
+
# [:place] Given a symbol or number, controls where this completion is placed in relation to existing ones. If a number, the
|
46
|
+
# completion is placed at that number. If the symbol :last, the completion is placed at the end regardless of completions
|
47
|
+
# defined after it. Use this symbol as a way of anchoring completions you want to remain at the end. Multiple declarations
|
48
|
+
# of :last are kept last in the order they are defined.
|
44
49
|
#
|
45
50
|
# ==== Examples:
|
46
51
|
# Bond.complete(:method=>'shoot') {|input| %w{to kill} }
|
47
52
|
# Bond.complete(:on=>/^((([a-z][^:.\(]*)+):)+/, :search=>false) {|input| Object.constants.grep(/#{input.matched[1]}/) }
|
48
|
-
# Bond.complete(:object=>ActiveRecord::Base, :search=>:underscore)
|
53
|
+
# Bond.complete(:object=>ActiveRecord::Base, :search=>:underscore, :place=>:last)
|
49
54
|
# Bond.complete(:object=>ActiveRecord::Base) {|input| input.object.class.instance_methods(false) }
|
50
55
|
# Bond.complete(:method=>'you', :search=>proc {|input, list| list.grep(/#{input}/i)} ) {|input| %w{Only Live Twice} }
|
51
56
|
def complete(options={}, &block)
|
@@ -54,6 +59,9 @@ module Bond
|
|
54
59
|
rescue InvalidMissionError
|
55
60
|
$stderr.puts "Invalid mission given. Mission needs an action and a condition."
|
56
61
|
false
|
62
|
+
rescue InvalidMissionActionError
|
63
|
+
$stderr.puts "Invalid mission action given. Make sure action responds to :call or refers to a predefined action that does."
|
64
|
+
false
|
57
65
|
rescue
|
58
66
|
$stderr.puts "Mission setup failed with:", $!
|
59
67
|
false
|
@@ -71,9 +79,13 @@ module Bond
|
|
71
79
|
# Defaults to Bond::Readline. Note that a plugin doesn't imply use with irb. Irb is joined to the hip with Readline.
|
72
80
|
# [:default_mission] A proc to be used as the default completion proc when no completions match or one fails. When in irb with completion
|
73
81
|
# enabled, uses irb completion. Otherwise defaults to a proc with an empty completion list.
|
82
|
+
# [:default_search] A symbol or proc to be used as the default search in completions. See Bond.complete's :search option for valid symbols.
|
74
83
|
# [:eval_binding] Specifies a binding to be used with Bond::Missions::ObjectMission. When in irb, defaults to irb's main binding. Otherwise
|
75
84
|
# defaults to TOPLEVEL_BINDING.
|
76
85
|
# [:debug] Boolean to print unexpected errors when autocompletion fails. Default is false.
|
86
|
+
#
|
87
|
+
# ==== Example:
|
88
|
+
# Bond.debrief :default_search=>:underscore, :default_mission=>:default
|
77
89
|
def debrief(options={})
|
78
90
|
config.merge! options
|
79
91
|
plugin_methods = %w{setup line_buffer}
|
@@ -85,6 +97,10 @@ module Bond
|
|
85
97
|
|
86
98
|
# Reports what completion mission and possible completions would happen for a given input. Helpful for debugging
|
87
99
|
# your completion missions.
|
100
|
+
# ==== Example:
|
101
|
+
# >> Bond.spy "shoot oct"
|
102
|
+
# Matches completion mission for method matching "shoot".
|
103
|
+
# Possible completions: ["octopussy"]
|
88
104
|
def spy(input)
|
89
105
|
agent.spy(input)
|
90
106
|
end
|
data/test/agent_test.rb
CHANGED
@@ -3,51 +3,49 @@ require File.join(File.dirname(__FILE__), 'test_helper')
|
|
3
3
|
class Bond::AgentTest < Test::Unit::TestCase
|
4
4
|
before(:all) {|e| Bond.debrief(:readline_plugin=>valid_readline_plugin) }
|
5
5
|
|
6
|
-
context "InvalidAgent" do
|
7
|
-
test "prints error if no action given for mission" do
|
8
|
-
capture_stderr { Bond.complete :on=>/blah/ }.should =~ /Invalid mission/
|
9
|
-
end
|
10
|
-
|
11
|
-
test "prints error if no condition given" do
|
12
|
-
capture_stderr { Bond.complete {|e| []} }.should =~ /Invalid mission/
|
13
|
-
end
|
14
|
-
|
15
|
-
test "prints error if invalid condition given" do
|
16
|
-
capture_stderr { Bond.complete(:on=>'blah') {|e| []} }.should =~ /Invalid mission/
|
17
|
-
end
|
18
|
-
|
19
|
-
test "prints error if setting mission fails unpredictably" do
|
20
|
-
Bond.agent.expects(:complete).raises(ArgumentError)
|
21
|
-
capture_stderr { Bond.complete(:on=>/blah/) {|e| [] } }.should =~ /Mission setup failed/
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
6
|
context "Agent" do
|
26
|
-
before(:each) {|e| Bond.agent.
|
7
|
+
before(:each) {|e| Bond.agent.reset }
|
27
8
|
|
28
9
|
test "chooses default mission if no missions match" do
|
29
|
-
|
10
|
+
complete(:on=>/bling/) {|e| [] }
|
30
11
|
Bond.agent.default_mission.expects(:execute)
|
31
|
-
|
12
|
+
tabtab 'blah'
|
32
13
|
end
|
33
14
|
|
34
15
|
test "chooses default mission if internal processing fails" do
|
35
|
-
|
16
|
+
complete(:on=>/bling/) {|e| [] }
|
36
17
|
Bond.agent.expects(:find_mission).raises
|
37
18
|
Bond.agent.default_mission.expects(:execute)
|
38
|
-
|
19
|
+
tabtab('bling')
|
20
|
+
end
|
21
|
+
|
22
|
+
test "completes in middle of line" do
|
23
|
+
complete(:object=>"Object")
|
24
|
+
tabtab(':man.f blah', ':man.f').include?(':man.freeze').should == true
|
25
|
+
end
|
26
|
+
|
27
|
+
test "places missions last when declared last" do
|
28
|
+
complete(:object=>"Symbol", :place=>:last)
|
29
|
+
complete(:method=>"man", :place=>:last) { }
|
30
|
+
complete(:on=>/man\s*(.*)/) {|e| e.matched[1] }
|
31
|
+
Bond.agent.missions.map {|e| e.class}.should == [Bond::Mission, Bond::Missions::ObjectMission, Bond::Missions::MethodMission]
|
32
|
+
tabtab('man ok').should == ['ok']
|
39
33
|
end
|
40
34
|
|
41
|
-
test "
|
42
|
-
|
43
|
-
|
35
|
+
test "places mission correctly for a place number" do
|
36
|
+
complete(:object=>"Symbol")
|
37
|
+
complete(:method=>"man") {}
|
38
|
+
complete(:on=>/man\s*(.*)/, :place=>1) {|e| e.matched[1] }
|
39
|
+
tabtab('man ok')
|
40
|
+
Bond.agent.missions.map {|e| e.class}.should == [Bond::Mission, Bond::Missions::ObjectMission, Bond::Missions::MethodMission]
|
41
|
+
tabtab('man ok').should == ['ok']
|
44
42
|
end
|
45
43
|
end
|
46
44
|
|
47
45
|
context "spy" do
|
48
46
|
before(:all) {
|
49
|
-
Bond.reset;
|
50
|
-
|
47
|
+
Bond.reset; complete(:on=>/end$/) { [] }; complete(:method=>'the') { %w{spy who loved me} }
|
48
|
+
complete(:object=>"Symbol")
|
51
49
|
}
|
52
50
|
|
53
51
|
test "detects basic mission" do
|
data/test/bond_test.rb
CHANGED
@@ -11,19 +11,51 @@ class BondTest < Test::Unit::TestCase
|
|
11
11
|
capture_stderr {Bond.debrief :readline_plugin=>Module.new{ def setup; end } }.should =~ /Invalid/
|
12
12
|
end
|
13
13
|
|
14
|
-
test "no error if valid readline_plugin" do
|
14
|
+
test "prints no error if valid readline_plugin" do
|
15
15
|
capture_stderr {Bond.debrief :readline_plugin=>valid_readline_plugin }.should == ''
|
16
16
|
end
|
17
17
|
|
18
18
|
test "sets default mission" do
|
19
|
-
default_mission = lambda {}
|
19
|
+
default_mission = lambda { %w{1 2 3}}
|
20
|
+
Bond.reset
|
20
21
|
Bond.debrief :default_mission=>default_mission, :readline_plugin=>valid_readline_plugin
|
21
|
-
|
22
|
+
tabtab('1').should == ['1']
|
23
|
+
end
|
24
|
+
|
25
|
+
test "sets default search" do
|
26
|
+
Bond.reset
|
27
|
+
Bond.debrief :default_search=>:underscore
|
28
|
+
complete(:method=>'blah') { %w{all_quiet on_the western_front}}
|
29
|
+
tabtab('blah a-q').should == ["all_quiet"]
|
30
|
+
Bond.reset
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "complete" do
|
35
|
+
test "prints error if no action given" do
|
36
|
+
capture_stderr { complete :on=>/blah/ }.should =~ /Invalid mission/
|
37
|
+
end
|
38
|
+
|
39
|
+
test "prints error if no condition given" do
|
40
|
+
capture_stderr { complete {|e| []} }.should =~ /Invalid mission/
|
41
|
+
end
|
42
|
+
|
43
|
+
test "prints error if invalid condition given" do
|
44
|
+
capture_stderr { complete(:on=>'blah') {|e| []} }.should =~ /Invalid mission/
|
45
|
+
end
|
46
|
+
|
47
|
+
test "prints error if invalid symbol action given" do
|
48
|
+
capture_stderr { complete(:on=>/blah/, :action=>:bling) }.should =~ /Invalid mission action/
|
49
|
+
end
|
50
|
+
|
51
|
+
test "prints error if setting mission fails unpredictably" do
|
52
|
+
Bond.agent.expects(:complete).raises(ArgumentError)
|
53
|
+
capture_stderr { complete(:on=>/blah/) {|e| [] } }.should =~ /Mission setup failed/
|
22
54
|
end
|
23
55
|
end
|
24
56
|
|
25
57
|
test "reset clears existing missions" do
|
26
|
-
|
58
|
+
complete(:on=>/blah/) {[]}
|
27
59
|
Bond.agent.missions.size.should_not == 0
|
28
60
|
Bond.reset
|
29
61
|
Bond.agent.missions.size.should == 0
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class Bond::CompletionTest < Test::Unit::TestCase
|
4
|
+
before(:all) { Bond.reset; Bond.debrief(:readline_plugin=>valid_readline_plugin); require 'bond/completion' }
|
5
|
+
|
6
|
+
test "completes object methods anywhere" do
|
7
|
+
matches = tabtab("blah :man.")
|
8
|
+
assert matches.size > 0
|
9
|
+
assert matches.all? {|e| e=~ /^:man/}
|
10
|
+
end
|
11
|
+
|
12
|
+
test "completes global variables anywhere" do
|
13
|
+
matches = tabtab("blah $-")
|
14
|
+
assert matches.size > 0
|
15
|
+
assert matches.all? {|e| e=~ /^\$-/}
|
16
|
+
end
|
17
|
+
|
18
|
+
test "completes absolute constants anywhere" do
|
19
|
+
tabtab("blah ::Arr").should == ["::Array"]
|
20
|
+
end
|
21
|
+
|
22
|
+
test "completes nested classes anywhere" do
|
23
|
+
tabtab("blah IRB::In").should == ["IRB::InputCompletor"]
|
24
|
+
end
|
25
|
+
|
26
|
+
test "completes symbols anywhere" do
|
27
|
+
Symbol.expects(:all_symbols).returns([:mah])
|
28
|
+
assert tabtab("blah :m").size > 0
|
29
|
+
end
|
30
|
+
|
31
|
+
test "completes string methods anywhere" do
|
32
|
+
tabtab("blah 'man'.f").include?('.freeze').should == true
|
33
|
+
end
|
34
|
+
|
35
|
+
test "methods don't swallow up default completion" do
|
36
|
+
Bond.agent.find_mission("Bond.complete(:method=>'blah') { Arr").should == nil
|
37
|
+
end
|
38
|
+
end
|
data/test/mission_test.rb
CHANGED
@@ -4,45 +4,56 @@ class Bond::MissionTest < Test::Unit::TestCase
|
|
4
4
|
before(:all) {|e| Bond.debrief(:readline_plugin=>valid_readline_plugin) }
|
5
5
|
|
6
6
|
context "mission" do
|
7
|
-
before(:each) {|e| Bond.agent.
|
7
|
+
before(:each) {|e| Bond.agent.reset }
|
8
8
|
test "completes" do
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
complete(:on=>/bling/) {|e| %w{ab cd fg hi}}
|
10
|
+
complete(:method=>'cool') {|e| [] }
|
11
|
+
tabtab('some bling f').should == %w{fg}
|
12
12
|
end
|
13
13
|
|
14
14
|
test "with method completes" do
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
complete(:on=>/bling/) {|e| [] }
|
16
|
+
complete(:method=>'cool') {|e| %w{ab cd ef gd} }
|
17
|
+
tabtab('cool c').should == %w{cd}
|
18
18
|
end
|
19
19
|
|
20
20
|
test "with method and quoted argument completes" do
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
complete(:on=>/bling/) {|e| [] }
|
22
|
+
complete(:method=>'cool') {|e| %w{ab cd ef ad} }
|
23
|
+
tabtab('cool "a').should == %w{ab ad}
|
24
24
|
end
|
25
25
|
|
26
26
|
test "with string method completes exact matches" do
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
complete(:method=>'cool?') {|e| [] }
|
28
|
+
complete(:method=>'cool') {|e| %w{ab cd ef gd} }
|
29
|
+
tabtab('cool c').should == %w{cd}
|
30
30
|
end
|
31
31
|
|
32
32
|
test "with regex method completes multiple methods" do
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
complete(:method=>/cool|ls/) {|e| %w{ab cd ef ad}}
|
34
|
+
tabtab("cool a").should == %w{ab ad}
|
35
|
+
tabtab("ls c").should == %w{cd}
|
36
36
|
end
|
37
37
|
|
38
38
|
test "with regexp condition completes" do
|
39
|
-
|
40
|
-
|
39
|
+
complete(:on=>/\s*'([^']+)$/, :search=>false) {|e| %w{coco for puffs}.grep(/#{e.matched[1]}/) }
|
40
|
+
tabtab("require 'ff").should == ['puffs']
|
41
41
|
end
|
42
42
|
|
43
43
|
test "with non-string completions completes" do
|
44
|
-
|
45
|
-
|
44
|
+
complete(:on=>/.*/) { [:one,:two,:three] }
|
45
|
+
tabtab('ok ').should == %w{one two three}
|
46
|
+
end
|
47
|
+
|
48
|
+
test "with symbol action completes" do
|
49
|
+
eval %[module ::Bond::Actions; def blah(input); %w{one two three}; end; end]
|
50
|
+
complete(:method=>'blah', :action=>:blah)
|
51
|
+
tabtab('blah ').should == %w{one two three}
|
52
|
+
end
|
53
|
+
|
54
|
+
test "with invalid action prints error" do
|
55
|
+
complete(:on=>/bling/) {|e| raise "whoops" }
|
56
|
+
capture_stderr { tabtab('bling') }.should =~ /bling.*whoops/m
|
46
57
|
end
|
47
58
|
end
|
48
59
|
|
data/test/object_mission_test.rb
CHANGED
@@ -2,42 +2,62 @@ require File.join(File.dirname(__FILE__), 'test_helper')
|
|
2
2
|
|
3
3
|
class Bond::ObjectMissionTest < Test::Unit::TestCase
|
4
4
|
before(:all) {|e| Bond.debrief(:readline_plugin=>valid_readline_plugin) }
|
5
|
-
before(:each) {|e| Bond.agent.
|
5
|
+
before(:each) {|e| Bond.agent.reset }
|
6
6
|
context "object mission" do
|
7
7
|
test "with default action completes" do
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
complete(:object=>"String")
|
9
|
+
complete(:on=>/man/) { %w{upper upster upful}}
|
10
|
+
tabtab("'man'.u").should == [".upcase!", ".unpack", ".untaint", ".upcase", ".upto"]
|
11
11
|
end
|
12
12
|
|
13
13
|
test "with regex condition completes" do
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
complete(:object=>/Str/) {|e| e.object.class.superclass.instance_methods(true) }
|
15
|
+
complete(:on=>/man/) { %w{upper upster upful}}
|
16
|
+
tabtab("'man'.u").should == [".untaint"]
|
17
17
|
end
|
18
18
|
|
19
19
|
test "with explicit action completes" do
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
complete(:object=>"String") {|e| e.object.class.superclass.instance_methods(true) }
|
21
|
+
complete(:on=>/man/) { %w{upper upster upful}}
|
22
|
+
tabtab("'man'.u").should == [".untaint"]
|
23
|
+
end
|
24
|
+
|
25
|
+
test "completes without including word break characters" do
|
26
|
+
complete(:object=>"Hash")
|
27
|
+
matches = tabtab("{}.f")
|
28
|
+
assert matches.size > 0
|
29
|
+
matches.all? {|e| !e.include?('{')}.should == true
|
30
|
+
end
|
31
|
+
|
32
|
+
test "completes nil, false and range objects" do
|
33
|
+
complete(:object=>"Object")
|
34
|
+
assert tabtab("nil.f").size > 0
|
35
|
+
assert tabtab("false.f").size > 0
|
36
|
+
assert tabtab("(1..10).f").size > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
test "completes hashes and arrays with spaces" do
|
40
|
+
complete(:object=>"Object")
|
41
|
+
assert tabtab("[1, 2].f").size > 0
|
42
|
+
assert tabtab("{:a =>1}.f").size > 0
|
23
43
|
end
|
24
44
|
|
25
45
|
test "ignores invalid invalid ruby" do
|
26
|
-
|
27
|
-
|
46
|
+
complete(:object=>"String")
|
47
|
+
tabtab("blah.upt").should == []
|
28
48
|
end
|
29
49
|
|
30
50
|
# needed to ensure Bond works in irbrc
|
31
51
|
test "doesn't evaluate irb binding on definition" do
|
32
52
|
Object.expects(:const_defined?).never
|
33
|
-
|
53
|
+
complete(:object=>"String")
|
34
54
|
end
|
35
55
|
|
36
56
|
test "sets binding to toplevel binding when not in irb" do
|
37
57
|
Object.expects(:const_defined?).with(:IRB).returns(false)
|
38
58
|
mission = Bond::Mission.create(:object=>'Symbol')
|
59
|
+
mission.class.expects(:eval).with(anything, ::TOPLEVEL_BINDING)
|
39
60
|
mission.matches?(':ok.')
|
40
|
-
mission.eval_binding.should == ::TOPLEVEL_BINDING
|
41
61
|
end
|
42
62
|
end
|
43
63
|
end
|
data/test/search_test.rb
CHANGED
@@ -2,41 +2,54 @@ require File.join(File.dirname(__FILE__), 'test_helper')
|
|
2
2
|
|
3
3
|
class Bond::SearchTest < Test::Unit::TestCase
|
4
4
|
before(:all) {|e| Bond.debrief(:readline_plugin=>valid_readline_plugin) }
|
5
|
-
before(:each) {|e| Bond.agent.
|
5
|
+
before(:each) {|e| Bond.agent.reset }
|
6
6
|
|
7
7
|
context "mission with search" do
|
8
8
|
test "false completes" do
|
9
|
-
|
10
|
-
|
9
|
+
complete(:on=>/cool '(.*)/, :search=>false) {|e| %w{coco for puffs}.grep(/#{e.matched[1]}/) }
|
10
|
+
tabtab("cool 'ff").should == ['puffs']
|
11
11
|
end
|
12
12
|
|
13
13
|
test "proc completes" do
|
14
|
-
|
15
|
-
|
14
|
+
complete(:method=>'blah', :search=>proc {|input, list| list.grep(/#{input}/)}) {|e| %w{coco for puffs} }
|
15
|
+
tabtab("blah 'ff").should == ['puffs']
|
16
16
|
end
|
17
17
|
|
18
18
|
test ":anywhere completes" do
|
19
|
-
|
20
|
-
|
19
|
+
complete(:method=>'blah', :search=>:anywhere) {|e| %w{coco for puffs} }
|
20
|
+
tabtab("blah 'ff").should == ['puffs']
|
21
21
|
end
|
22
22
|
|
23
23
|
test ":ignore_case completes" do
|
24
|
-
|
25
|
-
|
24
|
+
complete(:method=>'blah', :search=>:ignore_case) {|e| %w{Coco For PufFs} }
|
25
|
+
tabtab("blah 'pu").should == ['PufFs']
|
26
26
|
end
|
27
27
|
|
28
28
|
test ":underscore completes" do
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
complete(:on=>/blah/, :search=>:underscore) {|e| %w{and_one big_two can_three} }
|
30
|
+
tabtab("blah and").should == ['and_one']
|
31
|
+
tabtab("blah b-t").should == ['big_two']
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
test "underscore search doesn't pick up strings starting with __" do
|
36
|
+
completions = ["include?", "instance_variable_defined?", "__id__", "include_and_exclude?"]
|
37
|
+
complete(:method=>'blah', :search=>:underscore) { completions }
|
38
|
+
tabtab("blah i").should == ["include?", "instance_variable_defined?", "include_and_exclude?"]
|
39
|
+
end
|
40
|
+
|
41
|
+
test "underscore search can match first unique strings of each underscored word" do
|
42
|
+
completions = %w{so_long so_larger so_louder}
|
43
|
+
complete(:method=>'blah', :search=>:underscore) { completions }
|
44
|
+
tabtab("blah s-lo").should == %w{so_long so_louder}
|
45
|
+
tabtab("blah s-lou").should == %w{so_louder}
|
46
|
+
end
|
47
|
+
|
35
48
|
test "search handles completions with regex characters" do
|
36
49
|
completions = ['[doh]', '.*a', '?ok']
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
50
|
+
complete(:on=>/blah/) { completions }
|
51
|
+
tabtab('blah .').should == ['.*a']
|
52
|
+
tabtab('blah [').should == ['[doh]']
|
53
|
+
tabtab('blah ?').should == ['?ok']
|
41
54
|
end
|
42
55
|
end
|
data/test/test_helper.rb
CHANGED
@@ -42,13 +42,16 @@ class Test::Unit::TestCase
|
|
42
42
|
fake.string
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
45
|
+
def tabtab(full_line, last_word=full_line)
|
46
46
|
Bond.agent.stubs(:line_buffer).returns(full_line)
|
47
47
|
Bond.agent.call(last_word)
|
48
48
|
end
|
49
49
|
|
50
|
+
def complete(*args, &block)
|
51
|
+
Bond.complete(*args, &block)
|
52
|
+
end
|
53
|
+
|
50
54
|
def valid_readline_plugin
|
51
55
|
Module.new{ def setup; end; def line_buffer; end }
|
52
56
|
end
|
53
|
-
end
|
54
|
-
|
57
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cldwalker-bond
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriel Horner
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-22 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -31,7 +31,9 @@ files:
|
|
31
31
|
- ext/readline_line_buffer/extconf.rb
|
32
32
|
- ext/readline_line_buffer/readline_line_buffer.c
|
33
33
|
- lib/bond.rb
|
34
|
+
- lib/bond/actions.rb
|
34
35
|
- lib/bond/agent.rb
|
36
|
+
- lib/bond/completion.rb
|
35
37
|
- lib/bond/mission.rb
|
36
38
|
- lib/bond/missions/default_mission.rb
|
37
39
|
- lib/bond/missions/method_mission.rb
|
@@ -41,6 +43,7 @@ files:
|
|
41
43
|
- lib/bond/search.rb
|
42
44
|
- test/agent_test.rb
|
43
45
|
- test/bond_test.rb
|
46
|
+
- test/completion_test.rb
|
44
47
|
- test/mission_test.rb
|
45
48
|
- test/object_mission_test.rb
|
46
49
|
- test/search_test.rb
|
@@ -74,6 +77,7 @@ summary: "Mission: Easy custom autocompletion for arguments, methods and beyond.
|
|
74
77
|
test_files:
|
75
78
|
- test/agent_test.rb
|
76
79
|
- test/bond_test.rb
|
80
|
+
- test/completion_test.rb
|
77
81
|
- test/mission_test.rb
|
78
82
|
- test/object_mission_test.rb
|
79
83
|
- test/search_test.rb
|