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 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
- == Bugs
183
- The underscore search intermittently doesn't complete when it should and deletes the last
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 Bond to easily get its completion missions from a module.
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
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 1
4
- :patch: 1
4
+ :patch: 3
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 you should probably declare general
20
- # completions at the end.
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
@@ -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
- @missions << Mission.create(options.merge(:action=>block, :eval_binding=>@eval_binding))
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
- # Use line_buffer instead of input since it's more info
23
- (mission = find_mission(line_buffer)) ? mission.execute : default_mission.execute(input)
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
- # Handles creation of proper Mission class depending on the options passed.
14
- def self.create(options)
15
- if options[:method]
16
- Missions::MethodMission.new(options)
17
- elsif options[:object]
18
- Missions::ObjectMission.new(options)
19
- else
20
- new(options)
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
- @search = options.has_key?(:search) ? options[:search] : method(:default_search)
36
- @search = method("#{options[:search]}_search") if respond_to?("#{options[:search]}_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(*args)
50
- if args.empty?
51
- list = (@action.call(@input) || []).map {|e| e.to_s }
52
- list = @search ? @search.call(@input || '', list) : list
53
- @list_prefix ? list.map {|e| @list_prefix + e } : list
54
- else
55
- @action.call(*args)
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 : lambda {|e| [] }
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.quote(@method_condition.to_s) unless @method_condition.is_a?(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.quote(@object_condition.to_s)}$/ unless @object_condition.is_a?(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
- if match && eval_object(match) && (match = @evaled_object.class.ancestors.any? {|e| e.to_s =~ @object_condition })
19
- @list_prefix = @matched[1] + "."
20
- @input = @matched[3]
21
- @input.instance_variable_set("@object", @evaled_object)
22
- @input.instance_eval("def self.object; @object ; end")
23
- @action ||= lambda {|e| default_action(e.object) }
24
- else
25
- match = false
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 = begin eval("#{match[1]}", eval_binding); rescue Exception; nil end
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
- split_input = input.split("-").join("")
25
- list.select {|c|
26
- c.split("_").map {|g| g[0,1] }.join("") =~ /^#{Regexp.escape(split_input)}/ || c =~ /^#{Regexp.escape(input)}/
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.instance_eval("@missions = []") }
7
+ before(:each) {|e| Bond.agent.reset }
27
8
 
28
9
  test "chooses default mission if no missions match" do
29
- Bond.complete(:on=>/bling/) {|e| [] }
10
+ complete(:on=>/bling/) {|e| [] }
30
11
  Bond.agent.default_mission.expects(:execute)
31
- complete 'blah'
12
+ tabtab 'blah'
32
13
  end
33
14
 
34
15
  test "chooses default mission if internal processing fails" do
35
- Bond.complete(:on=>/bling/) {|e| [] }
16
+ complete(:on=>/bling/) {|e| [] }
36
17
  Bond.agent.expects(:find_mission).raises
37
18
  Bond.agent.default_mission.expects(:execute)
38
- complete('bling')
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 "prints error if action generates failure" do
42
- Bond.complete(:on=>/bling/) {|e| raise "whoops" }
43
- capture_stderr { complete('bling') }.should =~ /bling.*whoops/m
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; Bond.complete(:on=>/end$/) { [] }; Bond.complete(:method=>'the') { %w{spy who loved me} }
50
- Bond.complete(:object=>"Symbol")
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
- Bond.agent.default_mission.action.should == default_mission
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
- Bond.complete(:on=>/blah/) {[]}
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.instance_eval("@missions = []") }
7
+ before(:each) {|e| Bond.agent.reset }
8
8
  test "completes" do
9
- Bond.complete(:on=>/bling/) {|e| %w{ab cd fg hi}}
10
- Bond.complete(:method=>'cool') {|e| [] }
11
- complete('some bling f').should == %w{fg}
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
- Bond.complete(:on=>/bling/) {|e| [] }
16
- Bond.complete(:method=>'cool') {|e| %w{ab cd ef gd} }
17
- complete('cool c').should == %w{cd}
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
- Bond.complete(:on=>/bling/) {|e| [] }
22
- Bond.complete(:method=>'cool') {|e| %w{ab cd ef ad} }
23
- complete('cool "a').should == %w{ab ad}
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
- Bond.complete(:method=>'cool?') {|e| [] }
28
- Bond.complete(:method=>'cool') {|e| %w{ab cd ef gd} }
29
- complete('cool c').should == %w{cd}
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
- Bond.complete(:method=>/cool|ls/) {|e| %w{ab cd ef ad}}
34
- complete("cool a").should == %w{ab ad}
35
- complete("ls c").should == %w{cd}
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
- Bond.complete(:on=>/\s*'([^']+)$/, :search=>false) {|e| %w{coco for puffs}.grep(/#{e.matched[1]}/) }
40
- complete("require 'ff").should == ['puffs']
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
- Bond.complete(:on=>/.*/) { [:one,:two,:three] }
45
- complete('ok ').should == %w{one two three}
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
 
@@ -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.instance_eval("@missions = []") }
5
+ before(:each) {|e| Bond.agent.reset }
6
6
  context "object mission" do
7
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"]
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
- 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"]
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
- 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"]
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
- Bond.complete(:object=>"String")
27
- complete("blah.upt").should == []
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
- Bond.complete(:object=>"String")
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.instance_eval("@missions = []") }
5
+ before(:each) {|e| Bond.agent.reset }
6
6
 
7
7
  context "mission with search" do
8
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']
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
- Bond.complete(:method=>'blah', :search=>proc {|input, list| list.grep(/#{input}/)}) {|e| %w{coco for puffs} }
15
- complete("blah 'ff").should == ['puffs']
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
- Bond.complete(:method=>'blah', :search=>:anywhere) {|e| %w{coco for puffs} }
20
- complete("blah 'ff").should == ['puffs']
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
- Bond.complete(:method=>'blah', :search=>:ignore_case) {|e| %w{Coco For PufFs} }
25
- complete("blah 'pu").should == ['PufFs']
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
- 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']
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
- Bond.complete(:on=>/blah/) { completions }
38
- complete('blah .').should == ['.*a']
39
- complete('blah [').should == ['[doh]']
40
- complete('blah ?').should == ['?ok']
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 complete(full_line, last_word=full_line)
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.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-17 00:00:00 -04:00
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