bond 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG.rdoc 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