bond 0.1.1 → 0.1.3
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 +11 -0
- data/README.rdoc +12 -7
- data/VERSION.yml +1 -1
- data/lib/bond.rb +19 -3
- 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/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.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/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/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: 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 -04: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
|
@@ -76,6 +79,7 @@ summary: "Mission: Easy custom autocompletion for arguments, methods and beyond.
|
|
76
79
|
test_files:
|
77
80
|
- test/agent_test.rb
|
78
81
|
- test/bond_test.rb
|
82
|
+
- test/completion_test.rb
|
79
83
|
- test/mission_test.rb
|
80
84
|
- test/object_mission_test.rb
|
81
85
|
- test/search_test.rb
|