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.
@@ -1,2 +1,7 @@
1
+ == 0.1.1
2
+ * Added Bond.spy to debug completions
3
+ * Fixed object completion failing in irbrc
4
+ * Allow regex characters in completions
5
+
1
6
  == 0.1.0
2
7
  * Intial release. Whoop!
@@ -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.
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 1
4
- :patch: 0
4
+ :patch: 1
@@ -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) && plugin_methods.all? {|e| config[:readline_plugin].instance_methods.include?(e)}
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
@@ -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
- (mission = find_mission(input)) ? mission.execute : default_mission.execute(input)
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
- # No need to use what's passed to the completion proc when we can get the full line.
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
- all_input = line_buffer
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.
@@ -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
- @method = options.delete(:method)
5
- @method = Regexp.quote(@method.to_s) unless @method.is_a?(Regexp)
6
- options[:on] = /^\s*(#{@method})\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] || default_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]}", @eval_binding); rescue Exception; nil end
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
@@ -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
@@ -44,21 +44,26 @@ class Bond::AgentTest < Test::Unit::TestCase
44
44
  end
45
45
  end
46
46
 
47
- # TODO
48
- # test "default_mission set to a valid mission if irb doesn't exist" do
49
- # old_default_action = Bond.agent.default_mission.action
50
- # Bond.agent.instance_eval("@default_mission = @default_mission_action = nil")
51
- # Object.expects(:const_defined?).with(:IRB).returns(false)
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
- # test "binding set to toplevel binding if irb doesn't exist" do
58
- # old_binding = Bond.agent.eval_binding
59
- # Object.expects(:const_defined?).with(:IRB).returns(false)
60
- # Bond.agent.instance_eval("@eval_binding = nil")
61
- # Bond.agent.eval_binding.should == ::TOPLEVEL_BINDING
62
- # Bond.agent.instance_variable_set(:@eval_binding, old_binding)
63
- # end
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
@@ -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', 'f').should == %w{fg}
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', 'c').should == %w{cd}
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', 'a').should == %w{ab ad}
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', 'c').should == %w{cd}
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 no search option and matching completes" do
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 search proc completes" do
44
- Bond.complete(:method=>'blah', :search=>proc {|input, list| list.grep(/#{input}/)}) {|e| %w{coco for puffs} }
45
- complete("blah 'ff").should == ['puffs']
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
- test "ignores invalid objects" do
83
- Bond.complete(:object=>"String")
84
- complete("blah.upt").should == []
85
- end
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
@@ -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
@@ -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.0
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-16 00:00:00 -04:00
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