bond 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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