bond 0.1.0 → 0.1.1
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 +5 -0
- data/README.rdoc +0 -1
- data/VERSION.yml +1 -1
- data/lib/bond.rb +9 -2
- data/lib/bond/agent.rb +18 -4
- data/lib/bond/mission.rb +3 -3
- data/lib/bond/missions/method_mission.rb +4 -3
- data/lib/bond/missions/object_mission.rb +9 -3
- data/lib/bond/search.rb +4 -4
- data/test/agent_test.rb +21 -16
- data/test/mission_test.rb +13 -46
- data/test/object_mission_test.rb +43 -0
- data/test/search_test.rb +42 -0
- data/test/test_helper.rb +11 -0
- metadata +6 -2
data/CHANGELOG.rdoc
CHANGED
data/README.rdoc
CHANGED
@@ -186,6 +186,5 @@ character typed.
|
|
186
186
|
== Todo
|
187
187
|
* Allow Bond to easily get its completion missions from a module.
|
188
188
|
* Provide a set of Bond completions which can serve as a drop-in replacement for irb/completion.
|
189
|
-
* Bond.spy for easily viewing/debugging what completion mission matches a given input.
|
190
189
|
* Argument autocompletion by object class.
|
191
190
|
* Make replacement of existing completions not require doing a Bond.reset.
|
data/VERSION.yml
CHANGED
data/lib/bond.rb
CHANGED
@@ -65,7 +65,7 @@ module Bond
|
|
65
65
|
@agent = nil
|
66
66
|
end
|
67
67
|
|
68
|
-
# Debriefs Bond to set global defaults.
|
68
|
+
# Debriefs Bond to set global defaults. Call before defining completions.
|
69
69
|
# ==== Options:
|
70
70
|
# [:readline_plugin] Specifies a Bond plugin to interface with a Readline-like library. Available plugins are Bond::Readline and Bond::Rawline.
|
71
71
|
# Defaults to Bond::Readline. Note that a plugin doesn't imply use with irb. Irb is joined to the hip with Readline.
|
@@ -77,11 +77,18 @@ module Bond
|
|
77
77
|
def debrief(options={})
|
78
78
|
config.merge! options
|
79
79
|
plugin_methods = %w{setup line_buffer}
|
80
|
-
unless config[:readline_plugin].is_a?(Module) &&
|
80
|
+
unless config[:readline_plugin].is_a?(Module) &&
|
81
|
+
plugin_methods.all? {|e| config[:readline_plugin].instance_methods.map {|f| f.to_s}.include?(e)}
|
81
82
|
$stderr.puts "Invalid readline plugin set. Try again."
|
82
83
|
end
|
83
84
|
end
|
84
85
|
|
86
|
+
# Reports what completion mission and possible completions would happen for a given input. Helpful for debugging
|
87
|
+
# your completion missions.
|
88
|
+
def spy(input)
|
89
|
+
agent.spy(input)
|
90
|
+
end
|
91
|
+
|
85
92
|
def agent #:nodoc:
|
86
93
|
@agent ||= Agent.new(config)
|
87
94
|
end
|
data/lib/bond/agent.rb
CHANGED
@@ -19,7 +19,8 @@ module Bond
|
|
19
19
|
|
20
20
|
# This is where the action starts when a completion is initiated.
|
21
21
|
def call(input)
|
22
|
-
|
22
|
+
# Use line_buffer instead of input since it's more info
|
23
|
+
(mission = find_mission(line_buffer)) ? mission.execute : default_mission.execute(input)
|
23
24
|
rescue FailedExecutionError
|
24
25
|
$stderr.puts "", $!.message
|
25
26
|
rescue
|
@@ -30,10 +31,23 @@ module Bond
|
|
30
31
|
default_mission.execute(input)
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
|
+
def spy(input)
|
35
|
+
if (mission = find_mission(input))
|
36
|
+
if mission.is_a?(Missions::ObjectMission)
|
37
|
+
puts "Matches completion mission for object with an ancestor matching #{mission.object_condition.inspect}."
|
38
|
+
elsif mission.is_a?(Missions::MethodMission)
|
39
|
+
puts "Matches completion mission for method matching #{mission.method_condition.inspect}."
|
40
|
+
else
|
41
|
+
puts "Matches completion mission with condition #{mission.condition.inspect}."
|
42
|
+
end
|
43
|
+
puts "Possible completions: #{mission.execute.inspect}"
|
44
|
+
else
|
45
|
+
puts "Doesn't match a completion mission."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
34
49
|
def find_mission(input) #:nodoc:
|
35
|
-
|
36
|
-
@missions.find {|mission| mission.matches?(all_input) }
|
50
|
+
@missions.find {|mission| mission.matches?(input) }
|
37
51
|
end
|
38
52
|
|
39
53
|
# Default mission used by agent.
|
data/lib/bond/mission.rb
CHANGED
@@ -21,7 +21,7 @@ module Bond
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
attr_reader :action
|
24
|
+
attr_reader :action, :condition
|
25
25
|
OPERATORS = ["%", "&", "*", "**", "+", "-", "/", "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>", "[]", "[]=", "^"]
|
26
26
|
|
27
27
|
# Options are almost the same as those explained at Bond.complete. The only difference is that the action is passed
|
@@ -48,8 +48,8 @@ module Bond
|
|
48
48
|
# Called when a mission has been chosen to autocomplete.
|
49
49
|
def execute(*args)
|
50
50
|
if args.empty?
|
51
|
-
list = @action.call(@input) || []
|
52
|
-
list = @search ? @search.call(@input, list) : list
|
51
|
+
list = (@action.call(@input) || []).map {|e| e.to_s }
|
52
|
+
list = @search ? @search.call(@input || '', list) : list
|
53
53
|
@list_prefix ? list.map {|e| @list_prefix + e } : list
|
54
54
|
else
|
55
55
|
@action.call(*args)
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# Represents a completion mission specified by :method in Bond.complete.
|
2
2
|
class Bond::Missions::MethodMission < Bond::Mission
|
3
|
+
attr_reader :method_condition
|
3
4
|
def initialize(options={}) #:nodoc:
|
4
|
-
@
|
5
|
-
@
|
6
|
-
options[:on] = /^\s*(#{@
|
5
|
+
@method_condition = options.delete(:method)
|
6
|
+
@method_condition = Regexp.quote(@method_condition.to_s) unless @method_condition.is_a?(Regexp)
|
7
|
+
options[:on] = /^\s*(#{@method_condition})\s*['"]?(.*)$/
|
7
8
|
super
|
8
9
|
end
|
9
10
|
|
@@ -3,11 +3,13 @@
|
|
3
3
|
# an ancestor specified by :object.
|
4
4
|
class Bond::Missions::ObjectMission < Bond::Mission
|
5
5
|
#:stopdoc:
|
6
|
+
attr_reader :object_condition
|
7
|
+
|
6
8
|
def initialize(options={})
|
7
9
|
@object_condition = options.delete(:object)
|
8
10
|
@object_condition = /^#{Regexp.quote(@object_condition.to_s)}$/ unless @object_condition.is_a?(Regexp)
|
9
11
|
options[:on] = /^((\.?[^.]+)+)\.([^.]*)$/
|
10
|
-
@eval_binding = options[:eval_binding]
|
12
|
+
@eval_binding = options[:eval_binding]
|
11
13
|
super
|
12
14
|
end
|
13
15
|
|
@@ -27,11 +29,15 @@ class Bond::Missions::ObjectMission < Bond::Mission
|
|
27
29
|
|
28
30
|
def eval_object(match)
|
29
31
|
@matched = match
|
30
|
-
@evaled_object = begin eval("#{match[1]}",
|
32
|
+
@evaled_object = begin eval("#{match[1]}", eval_binding); rescue Exception; nil end
|
31
33
|
end
|
32
34
|
|
33
35
|
def default_action(obj)
|
34
|
-
obj.methods - OPERATORS
|
36
|
+
obj.methods.map {|e| e.to_s} - OPERATORS
|
37
|
+
end
|
38
|
+
|
39
|
+
def eval_binding
|
40
|
+
@eval_binding ||= default_eval_binding
|
35
41
|
end
|
36
42
|
|
37
43
|
def default_eval_binding
|
data/lib/bond/search.rb
CHANGED
@@ -3,17 +3,17 @@ module Bond
|
|
3
3
|
module Search
|
4
4
|
# Searches completions from the beginning of the string.
|
5
5
|
def default_search(input, list)
|
6
|
-
list.grep(/^#{input}/)
|
6
|
+
list.grep(/^#{Regexp.escape(input)}/)
|
7
7
|
end
|
8
8
|
|
9
9
|
# Searches completions anywhere in the string.
|
10
10
|
def anywhere_search(input, list)
|
11
|
-
list.grep(/#{input}/)
|
11
|
+
list.grep(/#{Regexp.escape(input)}/)
|
12
12
|
end
|
13
13
|
|
14
14
|
# Searches completions from the beginning and ignores case.
|
15
15
|
def ignore_case_search(input, list)
|
16
|
-
list.grep(/^#{input}/i)
|
16
|
+
list.grep(/^#{Regexp.escape(input)}/i)
|
17
17
|
end
|
18
18
|
|
19
19
|
# Searches completions from the beginning but also provides aliasing of underscored words.
|
@@ -23,7 +23,7 @@ module Bond
|
|
23
23
|
def underscore_search(input, list)
|
24
24
|
split_input = input.split("-").join("")
|
25
25
|
list.select {|c|
|
26
|
-
c.split("_").map {|g| g[0,1] }.join("") =~ /^#{split_input}/ || c =~ /^#{input}/
|
26
|
+
c.split("_").map {|g| g[0,1] }.join("") =~ /^#{Regexp.escape(split_input)}/ || c =~ /^#{Regexp.escape(input)}/
|
27
27
|
}
|
28
28
|
end
|
29
29
|
end
|
data/test/agent_test.rb
CHANGED
@@ -44,21 +44,26 @@ class Bond::AgentTest < Test::Unit::TestCase
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# Bond.agent.default_mission.is_a?(Bond::Mission).should == true
|
53
|
-
# Bond.agent.default_mission.action.respond_to?(:call).should == true
|
54
|
-
# Bond.agent.default_mission.instance_variable_set(:@action, old_default_action)
|
55
|
-
# end
|
47
|
+
context "spy" do
|
48
|
+
before(:all) {
|
49
|
+
Bond.reset; Bond.complete(:on=>/end$/) { [] }; Bond.complete(:method=>'the') { %w{spy who loved me} }
|
50
|
+
Bond.complete(:object=>"Symbol")
|
51
|
+
}
|
56
52
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
53
|
+
test "detects basic mission" do
|
54
|
+
capture_stdout { Bond.spy('the end')}.should =~ /end/
|
55
|
+
end
|
56
|
+
|
57
|
+
test "detects object mission" do
|
58
|
+
capture_stdout { Bond.spy(':dude.i')}.should =~ /object.*Symbol.*dude\.id/m
|
59
|
+
end
|
60
|
+
|
61
|
+
test "detects method mission" do
|
62
|
+
capture_stdout { Bond.spy('the ')}.should =~ /method.*the.*loved/m
|
63
|
+
end
|
64
|
+
|
65
|
+
test "detects no mission" do
|
66
|
+
capture_stdout { Bond.spy('blah')}.should =~ /Doesn't match/
|
67
|
+
end
|
68
|
+
end
|
64
69
|
end
|
data/test/mission_test.rb
CHANGED
@@ -8,25 +8,25 @@ class Bond::MissionTest < Test::Unit::TestCase
|
|
8
8
|
test "completes" do
|
9
9
|
Bond.complete(:on=>/bling/) {|e| %w{ab cd fg hi}}
|
10
10
|
Bond.complete(:method=>'cool') {|e| [] }
|
11
|
-
complete('some bling f'
|
11
|
+
complete('some bling f').should == %w{fg}
|
12
12
|
end
|
13
13
|
|
14
14
|
test "with method completes" do
|
15
15
|
Bond.complete(:on=>/bling/) {|e| [] }
|
16
16
|
Bond.complete(:method=>'cool') {|e| %w{ab cd ef gd} }
|
17
|
-
complete('cool c'
|
17
|
+
complete('cool c').should == %w{cd}
|
18
18
|
end
|
19
19
|
|
20
20
|
test "with method and quoted argument completes" do
|
21
21
|
Bond.complete(:on=>/bling/) {|e| [] }
|
22
22
|
Bond.complete(:method=>'cool') {|e| %w{ab cd ef ad} }
|
23
|
-
complete('cool "a'
|
23
|
+
complete('cool "a').should == %w{ab ad}
|
24
24
|
end
|
25
25
|
|
26
26
|
test "with string method completes exact matches" do
|
27
27
|
Bond.complete(:method=>'cool?') {|e| [] }
|
28
28
|
Bond.complete(:method=>'cool') {|e| %w{ab cd ef gd} }
|
29
|
-
complete('cool c'
|
29
|
+
complete('cool c').should == %w{cd}
|
30
30
|
end
|
31
31
|
|
32
32
|
test "with regex method completes multiple methods" do
|
@@ -35,53 +35,20 @@ class Bond::MissionTest < Test::Unit::TestCase
|
|
35
35
|
complete("ls c").should == %w{cd}
|
36
36
|
end
|
37
37
|
|
38
|
-
test "with
|
38
|
+
test "with regexp condition completes" do
|
39
39
|
Bond.complete(:on=>/\s*'([^']+)$/, :search=>false) {|e| %w{coco for puffs}.grep(/#{e.matched[1]}/) }
|
40
40
|
complete("require 'ff").should == ['puffs']
|
41
41
|
end
|
42
42
|
|
43
|
-
test "with
|
44
|
-
Bond.complete(:
|
45
|
-
complete(
|
46
|
-
end
|
47
|
-
|
48
|
-
test "with anywhere search completes" do
|
49
|
-
Bond.complete(:method=>'blah', :search=>:anywhere) {|e| %w{coco for puffs} }
|
50
|
-
complete("blah 'ff").should == ['puffs']
|
51
|
-
end
|
52
|
-
|
53
|
-
test "with ignore case search completes" do
|
54
|
-
Bond.complete(:method=>'blah', :search=>:ignore_case) {|e| %w{Coco For PufFs} }
|
55
|
-
complete("blah 'pu").should == ['PufFs']
|
56
|
-
end
|
57
|
-
|
58
|
-
test "with underscore search completes" do
|
59
|
-
Bond.complete(:on=>/blah/, :search=>:underscore) {|e| %w{and_one big_two can_three} }
|
60
|
-
complete("blah and").should == ['and_one']
|
61
|
-
complete("blah b-t").should == ['big_two']
|
62
|
-
end
|
63
|
-
|
64
|
-
test "with object and default action completes" do
|
65
|
-
Bond.complete(:object=>"String")
|
66
|
-
Bond.complete(:on=>/man/) { %w{upper upster upful}}
|
67
|
-
complete("'man'.u").should == ["'man'.upcase!", "'man'.unpack", "'man'.untaint", "'man'.upcase", "'man'.upto"]
|
68
|
-
end
|
69
|
-
|
70
|
-
test "with regex object completes" do
|
71
|
-
Bond.complete(:object=>/Str/) {|e| e.object.class.superclass.instance_methods(true) }
|
72
|
-
Bond.complete(:on=>/man/) { %w{upper upster upful}}
|
73
|
-
complete("'man'.u").should == ["'man'.untaint"]
|
74
|
-
end
|
75
|
-
|
76
|
-
test "with object and explicit action completes" do
|
77
|
-
Bond.complete(:object=>"String") {|e| e.object.class.superclass.instance_methods(true) }
|
78
|
-
Bond.complete(:on=>/man/) { %w{upper upster upful}}
|
79
|
-
complete("'man'.u").should == ["'man'.untaint"]
|
43
|
+
test "with non-string completions completes" do
|
44
|
+
Bond.complete(:on=>/.*/) { [:one,:two,:three] }
|
45
|
+
complete('ok ').should == %w{one two three}
|
80
46
|
end
|
47
|
+
end
|
81
48
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
49
|
+
test "default_mission set to a valid mission if irb doesn't exist" do
|
50
|
+
Object.expects(:const_defined?).with(:IRB).returns(false)
|
51
|
+
mission = Bond::Missions::DefaultMission.new
|
52
|
+
mission.action.respond_to?(:call).should == true
|
86
53
|
end
|
87
54
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class Bond::ObjectMissionTest < Test::Unit::TestCase
|
4
|
+
before(:all) {|e| Bond.debrief(:readline_plugin=>valid_readline_plugin) }
|
5
|
+
before(:each) {|e| Bond.agent.instance_eval("@missions = []") }
|
6
|
+
context "object mission" do
|
7
|
+
test "with default action completes" do
|
8
|
+
Bond.complete(:object=>"String")
|
9
|
+
Bond.complete(:on=>/man/) { %w{upper upster upful}}
|
10
|
+
complete("'man'.u").should == ["'man'.upcase!", "'man'.unpack", "'man'.untaint", "'man'.upcase", "'man'.upto"]
|
11
|
+
end
|
12
|
+
|
13
|
+
test "with regex condition completes" do
|
14
|
+
Bond.complete(:object=>/Str/) {|e| e.object.class.superclass.instance_methods(true) }
|
15
|
+
Bond.complete(:on=>/man/) { %w{upper upster upful}}
|
16
|
+
complete("'man'.u").should == ["'man'.untaint"]
|
17
|
+
end
|
18
|
+
|
19
|
+
test "with explicit action completes" do
|
20
|
+
Bond.complete(:object=>"String") {|e| e.object.class.superclass.instance_methods(true) }
|
21
|
+
Bond.complete(:on=>/man/) { %w{upper upster upful}}
|
22
|
+
complete("'man'.u").should == ["'man'.untaint"]
|
23
|
+
end
|
24
|
+
|
25
|
+
test "ignores invalid invalid ruby" do
|
26
|
+
Bond.complete(:object=>"String")
|
27
|
+
complete("blah.upt").should == []
|
28
|
+
end
|
29
|
+
|
30
|
+
# needed to ensure Bond works in irbrc
|
31
|
+
test "doesn't evaluate irb binding on definition" do
|
32
|
+
Object.expects(:const_defined?).never
|
33
|
+
Bond.complete(:object=>"String")
|
34
|
+
end
|
35
|
+
|
36
|
+
test "sets binding to toplevel binding when not in irb" do
|
37
|
+
Object.expects(:const_defined?).with(:IRB).returns(false)
|
38
|
+
mission = Bond::Mission.create(:object=>'Symbol')
|
39
|
+
mission.matches?(':ok.')
|
40
|
+
mission.eval_binding.should == ::TOPLEVEL_BINDING
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/test/search_test.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class Bond::SearchTest < Test::Unit::TestCase
|
4
|
+
before(:all) {|e| Bond.debrief(:readline_plugin=>valid_readline_plugin) }
|
5
|
+
before(:each) {|e| Bond.agent.instance_eval("@missions = []") }
|
6
|
+
|
7
|
+
context "mission with search" do
|
8
|
+
test "false completes" do
|
9
|
+
Bond.complete(:on=>/cool '(.*)/, :search=>false) {|e| %w{coco for puffs}.grep(/#{e.matched[1]}/) }
|
10
|
+
complete("cool 'ff").should == ['puffs']
|
11
|
+
end
|
12
|
+
|
13
|
+
test "proc completes" do
|
14
|
+
Bond.complete(:method=>'blah', :search=>proc {|input, list| list.grep(/#{input}/)}) {|e| %w{coco for puffs} }
|
15
|
+
complete("blah 'ff").should == ['puffs']
|
16
|
+
end
|
17
|
+
|
18
|
+
test ":anywhere completes" do
|
19
|
+
Bond.complete(:method=>'blah', :search=>:anywhere) {|e| %w{coco for puffs} }
|
20
|
+
complete("blah 'ff").should == ['puffs']
|
21
|
+
end
|
22
|
+
|
23
|
+
test ":ignore_case completes" do
|
24
|
+
Bond.complete(:method=>'blah', :search=>:ignore_case) {|e| %w{Coco For PufFs} }
|
25
|
+
complete("blah 'pu").should == ['PufFs']
|
26
|
+
end
|
27
|
+
|
28
|
+
test ":underscore completes" do
|
29
|
+
Bond.complete(:on=>/blah/, :search=>:underscore) {|e| %w{and_one big_two can_three} }
|
30
|
+
complete("blah and").should == ['and_one']
|
31
|
+
complete("blah b-t").should == ['big_two']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
test "search handles completions with regex characters" do
|
36
|
+
completions = ['[doh]', '.*a', '?ok']
|
37
|
+
Bond.complete(:on=>/blah/) { completions }
|
38
|
+
complete('blah .').should == ['.*a']
|
39
|
+
complete('blah [').should == ['[doh]']
|
40
|
+
complete('blah ?').should == ['?ok']
|
41
|
+
end
|
42
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -31,6 +31,17 @@ class Test::Unit::TestCase
|
|
31
31
|
fake.string
|
32
32
|
end
|
33
33
|
|
34
|
+
def capture_stdout(&block)
|
35
|
+
original_stdout = $stdout
|
36
|
+
$stdout = fake = StringIO.new
|
37
|
+
begin
|
38
|
+
yield
|
39
|
+
ensure
|
40
|
+
$stdout = original_stdout
|
41
|
+
end
|
42
|
+
fake.string
|
43
|
+
end
|
44
|
+
|
34
45
|
def complete(full_line, last_word=full_line)
|
35
46
|
Bond.agent.stubs(:line_buffer).returns(full_line)
|
36
47
|
Bond.agent.call(last_word)
|
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.1
|
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-17 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -42,6 +42,8 @@ files:
|
|
42
42
|
- test/agent_test.rb
|
43
43
|
- test/bond_test.rb
|
44
44
|
- test/mission_test.rb
|
45
|
+
- test/object_mission_test.rb
|
46
|
+
- test/search_test.rb
|
45
47
|
- test/test_helper.rb
|
46
48
|
has_rdoc: true
|
47
49
|
homepage: http://tagaholic.me/bond/
|
@@ -75,4 +77,6 @@ test_files:
|
|
75
77
|
- test/agent_test.rb
|
76
78
|
- test/bond_test.rb
|
77
79
|
- test/mission_test.rb
|
80
|
+
- test/object_mission_test.rb
|
81
|
+
- test/search_test.rb
|
78
82
|
- test/test_helper.rb
|