bond 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,13 @@
1
+ == 0.2.1
2
+ * Added Bond.load_yard_gems which generates argument completions from yard documentation.
3
+ * Added Bond.load_gems which loads completions from gem's directory.
4
+ * Added compatibility within emacs' inf-ruby mode (thanks to @pd).
5
+ * Added :eval_debug to Bond.config for debugging completions.
6
+ * Doesn't build or load c extension for ruby 1.9.2.
7
+ * Fixed argument completion of object containing method names bug.
8
+ * Fixed nested constants completion bug.
9
+ * Updated completions for Kernel#system, Module#public, Module#protected and Module#private.
10
+
1
11
  == 0.2.0
2
12
  * Added comprehensive argument completion per module, method and argument.
3
13
  * Added 60+ default method argument completions.
data/README.rdoc CHANGED
@@ -6,7 +6,8 @@ Bond is on a mission to improve irb's autocompletion. Aside from doing everythin
6
6
  Bond can autocomplete argument(s) to methods, uniquely completing per module, per method and per argument. Bond brings
7
7
  irb's completion closer to bash/zsh as it provides a configuration system and a DSL for creating custom completions
8
8
  and completion rules. With this configuration system, users can customize their irb autocompletions and share
9
- it with others. Bond is able to offer more than irb's completion since it uses a Readline C extension to get the full
9
+ it with others. Bond can also generate completions from yard documentation and load completions that ship with gems.
10
+ Bond is able to offer more than irb's completion since it uses a Readline C extension to get the full
10
11
  line of input when completing as opposed to irb's last-word approach.
11
12
 
12
13
  == Install
@@ -202,18 +203,18 @@ If on a Mac and using Editline as a Readline replacement (Readline::VERSION =~ /
202
203
  has good instructions for reinstalling ruby with the official Readline.
203
204
 
204
205
  == Credits
205
- Thanks to Csaba Hank for {providing the C extension}[http://www.creo.hu/~csaba/ruby/irb-enhancements/doc/files/README.html]
206
- which Bond uses to read Readline's full buffer. Thanks also goes out to Takao Kouji for {recently
207
- commiting}[http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/ext/readline/readline.c?view=diff&r1=24018&r2=24019]
208
- this Readline enhancement to ruby.
206
+ * Csaba Hank for {providing the C extension}[http://www.creo.hu/~csaba/ruby/irb-enhancements/doc/files/README.html] which Bond uses to read Readline's full buffer.
207
+ * Takao Kouji for {commiting}[http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/ext/readline/readline.c?view=diff&r1=24018&r2=24019] this Readline enhancement to ruby 1.9.2.
208
+ * pd for compatibility with emacs' inf-ruby mode.
209
209
 
210
210
  == Links
211
+ * http://tagaholic.me/2010/05/07/screencast-of-argument-autocompletion-for-methods-in-irb.html
211
212
  * http://tagaholic.me/2009/07/16/bond-from-irb-with-completion-love.html
212
213
  * http://tagaholic.me/2009/07/22/better-irb-completion-with-bond.html
213
214
  * http://tagaholic.me/2009/07/23/mini-irb-and-mini-script-console.html
214
215
 
215
216
  == Todo
216
- * Load completions that come with a gem easily.
217
+ * Generate method autocompletions for any arguments based on yardoc documentation.
217
218
  * Make completion actions more synonymous with argument types.
218
219
  * Cache expensive completion actions.
219
220
  * Ensure completions work when there is additional, unrelated text to the right of a completion.
data/Rakefile CHANGED
@@ -1,41 +1,35 @@
1
1
  require 'rake'
2
- begin
3
- require 'rcov/rcovtask'
2
+ require 'fileutils'
4
3
 
5
- Rcov::RcovTask.new do |t|
6
- t.libs << 'test'
7
- t.test_files = FileList['test/**/*_test.rb']
8
- t.rcov_opts = ["-T -x '/Library/Ruby/*'"]
9
- t.verbose = true
10
- end
11
- rescue LoadError
4
+ def gemspec
5
+ @gemspec ||= eval(File.read('gemspec'), binding, 'gemspec')
12
6
  end
13
7
 
14
- begin
15
- require 'jeweler'
16
- require File.dirname(__FILE__) + "/lib/bond/version"
8
+ desc "Build the gem"
9
+ task :gem=>:gemspec do
10
+ sh "gem build gemspec"
11
+ FileUtils.mkdir_p 'pkg'
12
+ FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg'
13
+ end
17
14
 
18
- Jeweler::Tasks.new do |s|
19
- s.name = "bond"
20
- s.version = Bond::VERSION
21
- s.summary = "Mission: Easy custom autocompletion for arguments, methods and beyond. Accomplished for irb and any other readline-like console environments."
22
- s.description = "Bond is on a mission to improve irb’s autocompletion. Aside from doing everything irb’s can do and fixing its quirks, Bond can autocomplete argument(s) to methods, uniquely completing per module, per method and per argument. Bond brings irb’s completion closer to bash/zsh as it provides a configuration system and a DSL for creating custom completions and completion rules. With this configuration system, users can customize their irb autocompletions and share it with others. Bond is able to offer more than irb’s completion since it uses a Readline C extension to get the full line of input when completing as opposed to irb’s last-word approach."
23
- s.email = "gabriel.horner@gmail.com"
24
- s.homepage = "http://tagaholic.me/bond/"
25
- s.authors = ["Gabriel Horner"]
26
- s.rubyforge_project = 'tagaholic'
27
- s.has_rdoc = true
28
- s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
29
- s.extensions = ["ext/readline_line_buffer/extconf.rb"]
30
- s.files = FileList["CHANGELOG.rdoc", "Rakefile", "README.rdoc", "LICENSE.txt", "{bin,lib,test,ext}/**/*"]
31
- end
15
+ desc "Install the gem locally"
16
+ task :install => :gem do
17
+ sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}}
18
+ end
32
19
 
33
- rescue LoadError
20
+ desc "Generate the gemspec"
21
+ task :generate do
22
+ puts gemspec.to_ruby
34
23
  end
35
24
 
36
- task :default => :test
25
+ desc "Validate the gemspec"
26
+ task :gemspec do
27
+ gemspec.validate
28
+ end
37
29
 
38
- desc 'Run specs with unit test style output'
30
+ desc 'Run tests'
39
31
  task :test do |t|
40
- sh 'bacon -q -Ilib test/*_test.rb'
32
+ sh 'bacon -q -Ilib -I. test/*_test.rb'
41
33
  end
34
+
35
+ task :default => :test
data/gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'rubygems' unless Object.const_defined?(:Gem)
3
+ require File.dirname(__FILE__) + "/lib/bond/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "bond"
7
+ s.version = Bond::VERSION
8
+ s.authors = ["Gabriel Horner"]
9
+ s.email = "gabriel.horner@gmail.com"
10
+ s.homepage = "http://tagaholic.me/bond/"
11
+ s.summary = "Mission: Easy custom autocompletion for arguments, methods and beyond. Accomplished for irb and any other readline-like console environments."
12
+ s.description = "Bond is on a mission to improve irb’s autocompletion. Aside from doing everything irb’s can do and fixing its quirks, Bond can autocomplete argument(s) to methods, uniquely completing per module, per method and per argument. Bond brings irb’s completion closer to bash/zsh as it provides a configuration system and a DSL for creating custom completions and completion rules. With this configuration system, users can customize their irb autocompletions and share it with others. Bond can also generate completions from yard documentation and load completions that ship with gems. Bond is able to offer more than irb’s completion since it uses a Readline C extension to get the full line of input when completing as opposed to irb’s last-word approach."
13
+ s.required_rubygems_version = ">= 1.3.6"
14
+ s.rubyforge_project = 'tagaholic'
15
+ s.add_development_dependency 'bacon'
16
+ s.add_development_dependency 'mocha'
17
+ s.add_development_dependency 'mocha-on-bacon'
18
+ s.files = Dir.glob(%w[{lib,test}/**/*.rb bin/* [A-Z]*.{txt,rdoc} ext/**/*.{rb,c}]) + %w{Rakefile gemspec}
19
+ s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
20
+ s.extensions = RUBY_VERSION < '1.9.2' ? ["ext/readline_line_buffer/extconf.rb"] : []
21
+ end
data/lib/bond.rb CHANGED
@@ -12,6 +12,7 @@ require 'bond/missions/method_mission'
12
12
  require 'bond/missions/object_mission'
13
13
  require 'bond/missions/anywhere_mission'
14
14
  require 'bond/missions/operator_method_mission'
15
+ require 'bond/yard'
15
16
 
16
17
  module Bond
17
18
  extend self
@@ -70,18 +71,32 @@ module Bond
70
71
  # When in irb, defaults to irb's current binding. Otherwise defaults to TOPLEVEL_BINDING.
71
72
  # [*:debug*] Boolean to show the stacktrace when autocompletion fails and raise exceptions in Rc.eval.
72
73
  # Default is false.
74
+ # [*:eval_debug*] Boolean to raise eval errors occuring when finding a matching completion. Useful to debug
75
+ # an incorrect completion. Default is false.
73
76
  def config; M.config; end
74
77
 
75
- # Starts Bond with a default set of completions that replace and improve irb's completion. Loads completion
76
- # files in the following order: lib/bond/completion.rb, optional ~/.bondrc, lib/bond/completions/*.rb,
77
- # optional ~/.bond/completions/*.rb and optional block. See Rc for the DSL to use in completion files and
78
- # in the block. See Bond.config for valid options.
79
- # ==== Example:
78
+ # Starts Bond with a default set of completions that replace and improve irb's completion. Loads completions
79
+ # in this order: lib/bond/completion.rb, lib/bond/completions/*.rb and the following optional completions:
80
+ # completions from :gems, completions from :yard_gems, ~/.bondrc, ~/.bond/completions/*.rb and from block. See
81
+ # Rc for the DSL to use in completion files and in the block. Valid options are Bond.config keys and the following:
82
+ # [*:gems*] Array of gems which have their completions loaded from lib/bond/completions/*.rb.
83
+ # [*:yard_gems*] Array of gems using yard documentation to generate completions. See Yard.
84
+ # ==== Examples:
85
+ # Bond.start :gems=>%w{hirb}
80
86
  # Bond.start(:default_search=>:ignore_case) do
81
87
  # complete(:method=>"Object#respond_to?") {|e| e.object.methods }
82
88
  # end
83
89
  def start(options={}, &block); M.start(options, &block); end
84
90
 
91
+ # Loads completions for gems that ship with them under lib/bond/completions/, relative to the gem's base directory.
92
+ def load_gems(*gems); M.load_gems(*gems); end
93
+
94
+ # Generates and loads completions for yardoc documented gems.
95
+ # ==== Options:
96
+ # [*:verbose*] Boolean which displays additional information when building yardoc. Default is false.
97
+ # [*:reload*] Rebuilds yard databases. Use when gems have changed versions. Default is false.
98
+ def load_yard_gems(*gems); Yard.load_yard_gems(*gems); end
99
+
85
100
  # An Agent who saves all Bond.complete missions and executes the correct one when a completion is called.
86
101
  def agent; M.agent; end
87
102
 
data/lib/bond/agent.rb CHANGED
@@ -37,7 +37,7 @@ module Bond
37
37
  rescue InvalidMissionError
38
38
  "Invalid #{$!.message} for completion with options: #{options.inspect}"
39
39
  rescue
40
- "Unexpected error while creating completion with options #{options.inspect}:\n#{$!}"
40
+ "Unexpected error while creating completion with options #{options.inspect} and message:\n#{$!}"
41
41
  end
42
42
 
43
43
  # Creates a mission and replaces the mission it matches if possible.
@@ -61,13 +61,14 @@ module Bond
61
61
  @missions = []
62
62
  end
63
63
 
64
- # This is where the action starts when a completion is initiated.
65
- def call(input)
66
- mission_input = @weapon.line_buffer
64
+ # This is where the action starts when a completion is initiated. Optional line_buffer
65
+ # overrides line buffer from readline plugin.
66
+ def call(input, line_buffer=nil)
67
+ mission_input = line_buffer || @weapon.line_buffer
67
68
  mission_input = $1 if mission_input !~ /#{Regexp.escape(input)}$/ && mission_input =~ /^(.*#{Regexp.escape(input)})/
68
69
  (mission = find_mission(mission_input)) ? mission.execute : default_mission.execute(Input.new(input))
69
- rescue FailedMissionError
70
- completion_error($!.message[0], "Completion Info: #{$!.message[1]}")
70
+ rescue FailedMissionError=>e
71
+ completion_error(e.message, "Completion Info: #{e.mission.match_message}")
71
72
  rescue
72
73
  completion_error "Failed internally with '#{$!.message}'.",
73
74
  "Please report this issue with debug on: Bond.config[:debug] = true."
@@ -87,6 +88,9 @@ module Bond
87
88
  else
88
89
  puts "Doesn't match a completion."
89
90
  end
91
+ rescue FailedMissionError=>e
92
+ puts e.mission.match_message, e.message,
93
+ "Matches for #{e.mission.condition.inspect} are #{e.mission.matched.to_a.inspect}"
90
94
  end
91
95
 
92
96
  def find_mission(input) #:nodoc:
@@ -4,10 +4,10 @@ complete :object=>"Object"
4
4
  complete :all_methods=>true
5
5
  complete :all_operator_methods=>true
6
6
  # classes and constants
7
- complete(:name=>:constants, :anywhere=>'(((::)?[A-Z][^:.\(]*)+)::?([^:.]*)') {|e|
7
+ complete(:name=>:constants, :anywhere=>'([A-Z][^. \(]*)::([^: .]*)') {|e|
8
8
  receiver = e.matched[2]
9
9
  candidates = eval("#{receiver}.constants | #{receiver}.methods") || []
10
- normal_search(e.matched[5], candidates).map {|e| receiver + "::" + e}
10
+ normal_search(e.matched[3], candidates).map {|e| receiver + "::" + e}
11
11
  }
12
12
  # absolute constants
13
13
  complete(:prefix=>'::', :anywhere=>'[A-Z][^:\.\(]*') {|e| Object.constants }
@@ -1,6 +1,8 @@
1
1
  complete(:methods=>%w{Kernel#raise Kernel#fail}) { objects_of(Class).select {|e| e < StandardError } }
2
- complete(:method=>%w{Kernel#system Kernel#exec}) {|e|
3
- ENV['PATH'].split(File::PATH_SEPARATOR).uniq.map {|e| Dir.entries(e) }.flatten.uniq - ['.', '..']
2
+ complete(:methods=>%w{Kernel#system Kernel#exec}) {|e|
3
+ ENV['PATH'].split(File::PATH_SEPARATOR).uniq.map {|e|
4
+ File.directory?(e) ? Dir.entries(e) : []
5
+ }.flatten.uniq - ['.', '..']
4
6
  }
5
7
  complete(:method=>"Kernel#require", :search=>:files) {
6
8
  paths = $:.map {|e| Dir["#{e}/**/*.{rb,bundle,dll,so}"].map {|f| f.sub(e+'/', '') } }.flatten
@@ -1,7 +1,10 @@
1
1
  complete(:methods=>%w{const_get const_set const_defined? remove_const}, :class=>"Module#") {|e| e.object.constants }
2
2
  complete(:methods=>%w{class_variable_defined? class_variable_get class_variable_set remove_class_variable},
3
3
  :class=>"Module#") {|e| e.object.class_variables }
4
- complete(:methods=>%w{instance_method method_defined? module_function public private protected remove_method undef_method},
4
+ complete(:methods=>%w{instance_method method_defined? module_function remove_method undef_method},
5
5
  :class=>"Module#") {|e| e.object.instance_methods }
6
+ complete(:method=>"Module#public") {|e| e.object.private_instance_methods + e.object.protected_instance_methods }
7
+ complete(:method=>"Module#private") {|e| e.object.instance_methods + e.object.protected_instance_methods }
8
+ complete(:method=>"Module#protected") {|e| e.object.instance_methods + e.object.private_instance_methods }
6
9
  complete(:methods=>%w{< <= <=> > >= include? include}, :class=>"Module#", :search=>:modules) { objects_of(Module) }
7
10
  complete(:method=>'Module#alias_method') {|e| e.argument > 1 ? e.object.instance_methods : [] }
data/lib/bond/m.rb CHANGED
@@ -6,7 +6,7 @@ module Bond
6
6
  # See Bond.complete
7
7
  def complete(options={}, &block)
8
8
  if (result = agent.complete(options, &block)).is_a?(String)
9
- $stderr.puts result
9
+ $stderr.puts "Bond Error: "+result
10
10
  false
11
11
  else
12
12
  true
@@ -16,7 +16,7 @@ module Bond
16
16
  # See Bond.recomplete
17
17
  def recomplete(options={}, &block)
18
18
  if (result = agent.recomplete(options, &block)).is_a?(String)
19
- $stderr.puts result
19
+ $stderr.puts "Bond Error: "+result
20
20
  false
21
21
  else
22
22
  true
@@ -50,7 +50,7 @@ module Bond
50
50
  plugin_methods = %w{setup line_buffer}
51
51
  unless config[:readline_plugin].is_a?(Module) &&
52
52
  plugin_methods.all? {|e| config[:readline_plugin].instance_methods.map {|f| f.to_s}.include?(e)}
53
- $stderr.puts "Invalid readline plugin set. Try again."
53
+ $stderr.puts "Bond Error: Invalid readline plugin given."
54
54
  end
55
55
  end
56
56
 
@@ -62,12 +62,28 @@ module Bond
62
62
  true
63
63
  end
64
64
 
65
+ def load_gem_completion(rubygem) #:nodoc:
66
+ (dir = find_gem_file(rubygem, File.join(rubygem, '..', 'bond'))) ?
67
+ load_dir(dir) : $stderr.puts("Bond Error: No completions found for gem '#{rubygem}'.")
68
+ end
69
+
70
+ # Finds the full path to a gem's file relative it's load path directory. Returns nil if not found.
71
+ def find_gem_file(rubygem, file)
72
+ begin gem(rubygem); rescue Exception; end
73
+ (dir = $:.find {|e| File.exists?(File.join(e, file)) }) && File.join(dir, file)
74
+ end
75
+
76
+ def load_gems(*gems) #:nodoc:
77
+ gems.select {|e| load_gem_completion(e) }
78
+ end
79
+
65
80
  def load_completions #:nodoc:
66
81
  load_file File.join(File.dirname(__FILE__), 'completion.rb')
82
+ load_dir File.dirname(__FILE__)
83
+ load_gems *config[:gems] if config[:gems]
84
+ Yard.load_yard_gems *config[:yard_gems] if config[:yard_gems]
67
85
  load_file(File.join(home,'.bondrc')) if File.exists?(File.join(home, '.bondrc'))
68
- [File.dirname(__FILE__), File.join(home, '.bond')].each do |base_dir|
69
- load_dir(base_dir)
70
- end
86
+ load_dir File.join(home, '.bond')
71
87
  end
72
88
 
73
89
  # Loads a completion file in Rc namespace.
@@ -77,7 +93,8 @@ module Bond
77
93
  $stderr.puts "Bond Error: Completion file '#{file}' failed to load with:", e.message
78
94
  end
79
95
 
80
- def load_dir(base_dir) #:nodoc:
96
+ # Loads completion files in given directory.
97
+ def load_dir(base_dir)
81
98
  if File.exists?(dir = File.join(base_dir, 'completions'))
82
99
  Dir[dir + '/*.rb'].each {|file| load_file(file) }
83
100
  end
data/lib/bond/mission.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  module Bond
2
2
  # Occurs when a mission is incorrectly defined.
3
3
  class InvalidMissionError < StandardError; end
4
- # Occurs when a mission or search action fails.
5
- class FailedMissionError < StandardError; end
4
+ # Occurs when a mission fails.
5
+ class FailedMissionError < StandardError
6
+ # Mission that failed
7
+ attr_reader :mission
8
+ def initialize(mission); @mission = mission; end #:nodoc:
9
+ end
6
10
 
7
11
  # Represents a completion rule, given a condition (:on) on which to match and an action
8
12
  # (block or :action) with which to generate possible completions.
@@ -83,7 +87,7 @@ module Bond
83
87
  message = $!.is_a?(NoMethodError) && !Rc.respond_to?("#{search}_search") ?
84
88
  "Completion search '#{search}' doesn't exist." :
85
89
  "Failed during completion search with '#{$!.message}'."
86
- raise FailedMissionError, [message, match_message]
90
+ raise FailedMissionError.new(self), message
87
91
  end
88
92
 
89
93
  # Calls the action to generate an array of possible completions.
@@ -93,7 +97,7 @@ module Bond
93
97
  message = $!.is_a?(NoMethodError) && !@action.respond_to?(:call) &&
94
98
  !Rc.respond_to?(@action) ? "Completion action '#{@action}' doesn't exist." :
95
99
  "Failed during completion action with '#{$!.message}'."
96
- raise FailedMissionError, [message, match_message]
100
+ raise FailedMissionError.new(self), message
97
101
  end
98
102
 
99
103
  # A message used to explains under what conditions a mission matched the user input.
@@ -131,6 +135,7 @@ module Bond
131
135
  @evaled_object = self.class.current_eval(obj, eval_binding)
132
136
  true
133
137
  rescue Exception
138
+ raise FailedMissionError.new(self), "Match failed during eval of '#{obj}'." if Bond.config[:eval_debug]
134
139
  false
135
140
  end
136
141
 
@@ -76,8 +76,9 @@ module Bond
76
76
  end
77
77
  end
78
78
 
79
+ raise InvalidMissionError, "array :method" if options[:method].is_a?(Array)
79
80
  meths = options[:methods] || Array(options[:method])
80
- raise InvalidMissionError, ":method(s)" unless meths.all? {|e| e.is_a?(String) }
81
+ raise InvalidMissionError, "non-string :method(s)" unless meths.all? {|e| e.is_a?(String) }
81
82
  if options[:class].is_a?(String)
82
83
  options[:class] << '#' unless options[:class][/[#.]$/]
83
84
  meths.map! {|e| options[:class] + e }
@@ -148,7 +149,7 @@ module Bond
148
149
  end
149
150
 
150
151
  self.reset
151
- OBJECTS = %w{\S*?} + Mission::OBJECTS
152
+ OBJECTS = Mission::OBJECTS + %w{\S*?}
152
153
  CONDITION = %q{(OBJECTS)\.?(METHODS)(?:\s+|\()(['":])?(.*)$}
153
154
 
154
155
  #:stopdoc:
data/lib/bond/readline.rb CHANGED
@@ -7,28 +7,7 @@ module Bond
7
7
  # Loads the readline-like library and sets the completion_proc to the given agent.
8
8
  def setup(agent)
9
9
  require 'readline'
10
- begin
11
- require 'readline_line_buffer'
12
- rescue LoadError
13
- $stderr.puts "Failed to load readline_line_buffer extension. Falling back on RubyInline extension."
14
- require 'inline'
15
- eval %[
16
- module ::Readline
17
- inline do |builder|
18
- %w(<errno.h> <stdio.h> <readline/readline.h>).each{|h| builder.include h }
19
- builder.c_raw_singleton <<-EOC
20
- static VALUE line_buffer(VALUE self)
21
- {
22
- rb_secure(4);
23
- if (rl_line_buffer == NULL)
24
- return Qnil;
25
- return rb_tainted_str_new2(rl_line_buffer);
26
- }
27
- EOC
28
- end
29
- end
30
- ]
31
- end
10
+ load_extension unless ::Readline.respond_to?(:line_buffer)
32
11
 
33
12
  # Reinforcing irb defaults
34
13
  ::Readline.completion_append_character = nil
@@ -42,6 +21,29 @@ module Bond
42
21
  end
43
22
  end
44
23
 
24
+ def load_extension
25
+ require 'readline_line_buffer'
26
+ rescue LoadError
27
+ $stderr.puts "Bond Error: Failed to load readline_line_buffer extension. Falling back on RubyInline extension."
28
+ require 'inline'
29
+ eval %[
30
+ module ::Readline
31
+ inline do |builder|
32
+ %w(<errno.h> <stdio.h> <readline/readline.h>).each{|h| builder.include h }
33
+ builder.c_raw_singleton <<-EOC
34
+ static VALUE line_buffer(VALUE self)
35
+ {
36
+ rb_secure(4);
37
+ if (rl_line_buffer == NULL)
38
+ return Qnil;
39
+ return rb_tainted_str_new2(rl_line_buffer);
40
+ }
41
+ EOC
42
+ end
43
+ end
44
+ ]
45
+ end
46
+
45
47
  # Returns full line of what the user has typed.
46
48
  def line_buffer
47
49
  ::Readline.line_buffer
data/lib/bond/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Bond
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
data/lib/bond/yard.rb ADDED
@@ -0,0 +1,73 @@
1
+ module Bond
2
+ # Generates method autocompletions for gems that use {yard}[http://yardoc.org] documentation. Currently
3
+ # generates completions for methods that take a hash of options and have been documented with @option.
4
+ module Yard
5
+ extend self
6
+
7
+ # :stopdoc:
8
+ def load_yard_gems(*gems)
9
+ @options = gems[-1].is_a?(Hash) ? gems.pop : {}
10
+ require 'yard'
11
+ raise LoadError unless YARD::VERSION >= '0.5.2'
12
+ gems.select {|e| load_yard_gem(e) }
13
+ rescue LoadError
14
+ $stderr.puts "Bond Error: yard gem (version >= 0.5.2) not installed "
15
+ end
16
+
17
+ def load_yard_gem(rubygem)
18
+ raise("Unable to find gem.") unless (yardoc = find_yardoc(rubygem))
19
+ completion_file = File.join(dir('yard_completions'), rubygem+'.rb')
20
+ create_completion_file(yardoc, completion_file) if !File.exists?(completion_file) || @options[:reload]
21
+ M.load_file completion_file
22
+ rescue
23
+ $stderr.puts "Bond Error: Didn't load yard completions for gem '#{rubygem}'. #{$!.message}"
24
+ end
25
+
26
+ def create_completion_file(yardoc, completion_file)
27
+ YARD::Registry.load!(yardoc)
28
+ methods_hash = find_methods_with_options
29
+ body = generate_method_completions(methods_hash)
30
+ File.open(completion_file, 'w') {|e| e.write body }
31
+ end
32
+
33
+ def find_yardoc(rubygem)
34
+ (file = YARD::Registry.yardoc_file_for_gem(rubygem) rescue nil) and return(file)
35
+ if (file = M.find_gem_file(rubygem, rubygem+'.rb'))
36
+ output_dir = File.join(dir('.yardocs'), rubygem)
37
+ cmd = ['yardoc', '-n', '-b', output_dir]
38
+ cmd << '-q' unless @options[:verbose]
39
+ cmd += ['-c', output_dir] unless @options[:reload]
40
+ cmd += [file, File.expand_path(file+'/..')+"/#{rubygem}/**/*.rb"]
41
+ puts "Bond: "+cmd.join(' ') if @options[:verbose]
42
+ puts "Bond: Building/loading #{rubygem}'s .yardoc database ..."
43
+ system *cmd
44
+ output_dir
45
+ end
46
+ end
47
+
48
+ def dir(subdir)
49
+ (@dirs ||= {})[subdir] ||= begin
50
+ require 'fileutils'
51
+ FileUtils.mkdir_p File.join(M.home, '.bond', subdir)
52
+ File.join(M.home, '.bond', subdir)
53
+ end
54
+ end
55
+
56
+ def find_methods_with_options
57
+ YARD::Registry.all(:method).inject({}) {|a,m|
58
+ opts = m.tags.select {|e| e.is_a?(YARD::Tags::OptionTag) }.map {|e| e.pair.name }
59
+ a[m.path] = opts if !opts.empty? && m.path
60
+ a
61
+ }
62
+ end
63
+
64
+ def generate_method_completions(methods_hash)
65
+ methods_hash.map do |meth, options|
66
+ options.map! {|e| e.sub(/^:/, '') }
67
+ meth = meth.sub(/#initialize$/, '.new')
68
+ %Q[complete(:method=>'#{meth}') {\n #{options.inspect}\n}]
69
+ end.join("\n")
70
+ end
71
+ #:startdoc:
72
+ end
73
+ end
data/test/agent_test.rb CHANGED
@@ -18,6 +18,11 @@ describe "Agent" do
18
18
  errors[1].should =~ /Please/
19
19
  end
20
20
 
21
+ it "allows the readline buffer to be provided as an argument" do
22
+ Bond.agent.weapon.stubs(:line_buffer).raises(Exception)
23
+ should.not.raise { Bond.agent.call('bl', 'bl foo') }
24
+ end
25
+
21
26
  def complete_error(msg)
22
27
  lambda {|e|
23
28
  e[0].should =~ msg
@@ -113,11 +118,15 @@ describe "Agent" do
113
118
  end
114
119
 
115
120
  it "with invalid :method prints error" do
116
- complete_prints_error(/Invalid :method\(s\)/, :method=>true) {}
121
+ complete_prints_error(/Invalid.*:method\(s\)/, :method=>true) {}
122
+ end
123
+
124
+ it "with invalid array :method prints error" do
125
+ complete_prints_error(/Invalid array :method/, :method=>%w{one two}) {}
117
126
  end
118
127
 
119
128
  it "with invalid :methods prints error" do
120
- complete_prints_error(/Invalid :method\(s\)/, :methods=>[:blah]) {}
129
+ complete_prints_error(/Invalid.*:method\(s\)/, :methods=>[:blah]) {}
121
130
  end
122
131
 
123
132
  it "with invalid :action for method completion prints error" do
@@ -24,7 +24,7 @@ describe "anywhere mission" do
24
24
  complete(:anywhere=>'\$[^\s.]*', :search=>false) {|e|
25
25
  global_variables.grep(/^#{Regexp.escape(e.matched[1])}/)
26
26
  }
27
- tab("$LO").should == ["$LOAD_PATH", "$LOADED_FEATURES"]
27
+ tab("$LO").sort.should == ["$LOADED_FEATURES", "$LOAD_PATH"]
28
28
  end
29
29
 
30
30
  it 'with :prefix completes' do
data/test/bond_test.rb CHANGED
@@ -2,36 +2,79 @@ require File.join(File.dirname(__FILE__), 'test_helper')
2
2
 
3
3
  describe "Bond" do
4
4
  describe "start" do
5
+ def start(options={}, &block)
6
+ Bond.start({:readline_plugin=>valid_readline_plugin}.merge(options), &block)
7
+ end
8
+
5
9
  before { M.instance_eval("@agent = @config = nil"); M.expects(:load_completions) }
6
10
  it "prints error if readline_plugin is not a module" do
7
- capture_stderr { Bond.start :readline_plugin=>false }.should =~ /Invalid/
11
+ capture_stderr { start :readline_plugin=>false }.should =~ /Invalid/
8
12
  end
9
13
 
10
14
  it "prints error if readline_plugin doesn't have all required methods" do
11
- capture_stderr {Bond.start :readline_plugin=>Module.new{ def setup(arg); end } }.should =~ /Invalid/
15
+ capture_stderr {start :readline_plugin=>Module.new{ def setup(arg); end } }.should =~ /Invalid/
12
16
  end
13
17
 
14
18
  it "prints no error if valid readline_plugin" do
15
- capture_stderr {Bond.start :readline_plugin=>valid_readline_plugin }.should == ''
19
+ capture_stderr { start }.should == ''
16
20
  end
17
21
 
18
22
  it "sets default mission" do
19
- Bond.start :default_mission=>lambda {|e| %w{1 2 3}}, :readline_plugin=>valid_readline_plugin
23
+ start :default_mission=>lambda {|e| %w{1 2 3}}
20
24
  tab('1').should == ['1']
21
25
  end
22
26
 
23
27
  it "sets default search" do
24
- Bond.start :default_search=>:anywhere, :readline_plugin=>valid_readline_plugin
28
+ start :default_search=>:anywhere
25
29
  complete(:on=>/blah/) { %w{all_quiet on_the western_front}}
26
30
  tab('blah qu').should == ["all_quiet"]
27
31
  end
28
32
 
29
33
  it "defines completion in block" do
30
- Bond.start do
31
- complete(:on=>/blah/) { %w{all_quiet on_the western_front}}
32
- end
34
+ start { complete(:on=>/blah/) { %w{all_quiet on_the western_front}} }
33
35
  tab('blah all').should == ["all_quiet"]
34
36
  end
37
+
38
+ after_all { M.debrief :readline_plugin=>valid_readline_plugin; M.reset }
39
+ end
40
+
41
+ describe "start with :gems" do
42
+ before {
43
+ File.stubs(:exists?).returns(true)
44
+ M.stubs(:load_file)
45
+ }
46
+
47
+ it "attempts to load gem" do
48
+ M.stubs(:load_dir)
49
+ M.expects(:gem).twice
50
+ start(:gems=>%w{one two})
51
+ end
52
+
53
+ it "rescues nonexistent gem" do
54
+ M.stubs(:load_dir)
55
+ M.expects(:gem).raises(LoadError)
56
+ should.not.raise { start(:gems=>%w{blah}) }
57
+ end
58
+
59
+ it "rescues nonexistent method 'gem'" do
60
+ M.stubs(:load_dir)
61
+ M.expects(:gem).raises(NoMethodError)
62
+ should.not.raise { start(:gems=>%w{blah}) }
63
+ end
64
+
65
+ it "prints error if gem completion not found" do
66
+ M.stubs(:load_dir)
67
+ M.expects(:find_gem_file).returns(nil)
68
+ capture_stderr { start(:gems=>%w{invalid}) }.should =~ /No completions.*'invalid'/
69
+ end
70
+
71
+ it "loads gem completion file" do
72
+ M.expects(:load_dir)
73
+ M.expects(:load_dir).with(File.join($:[0], 'awesome', '..', 'bond'))
74
+ M.expects(:load_dir)
75
+ M.expects(:gem)
76
+ start(:gems=>%w{awesome})
77
+ end
35
78
  after_all { M.debrief :readline_plugin=>valid_readline_plugin; M.reset }
36
79
  end
37
80
 
@@ -24,9 +24,13 @@ describe "Completion" do
24
24
  }.should == ''
25
25
  end
26
26
 
27
+ it "completes nested classes greater than 2 levels" do
28
+ eval %[module ::Foo; module Bar; module Baz; end; end; end]
29
+ tab("Foo::Bar::B").should == %w{Foo::Bar::Baz}
30
+ end
31
+
27
32
  it "completes nested classes anywhere" do
28
- mock_irb
29
- tab("blah IRB::In").should == ["IRB::InputCompletor"]
33
+ tab("module Blah; include Bond::Sea").should == ["Bond::Search"]
30
34
  end
31
35
 
32
36
  it "completes symbols anywhere" do
@@ -39,6 +43,10 @@ describe "Completion" do
39
43
  tab("%w{ab bc cd}.delete(").should == %w{ab bc cd}
40
44
  end
41
45
 
46
+ it "completes method arguments when object contains method names" do
47
+ tab("%w{find ab cd}.delete ").should == %w{find ab cd}
48
+ end
49
+
42
50
  it "completes hash coming from a method" do
43
51
  tab('Bond.config[:r').should == ["Bond.config[:readline_plugin"]
44
52
  end
data/test/yard_test.rb ADDED
@@ -0,0 +1,114 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ module YARD; VERSION = '0.5.2'; module Registry; end; end
4
+
5
+ describe 'Bond.load_yard_gems' do
6
+ def load_yard_gems(*args)
7
+ args = ['blah'] if args.empty?
8
+ Bond.load_yard_gems(*args)
9
+ end
10
+
11
+ it 'prints error if unable to require yard' do
12
+ Yard.expects(:require).with('yard').raises(LoadError)
13
+ capture_stderr { load_yard_gems }.should =~ /yard.*not installed/
14
+ end
15
+
16
+ it 'prints error if old version of yard' do
17
+ YARD.send :remove_const, :VERSION
18
+ YARD::VERSION = '0.5.0'
19
+ Yard.expects(:require).with('yard')
20
+ capture_stderr { load_yard_gems }.should =~ /yard.*not installed/
21
+ YARD.send :remove_const, :VERSION
22
+ YARD::VERSION = '0.5.2'
23
+ end
24
+
25
+ it 'prints error if no yardoc found' do
26
+ Yard.expects(:require).with('yard')
27
+ Yard.expects(:find_yardoc).returns(nil)
28
+ capture_stderr { load_yard_gems('bond') }.should =~ /Didn't.*'bond'.* Unable to find/
29
+ end
30
+
31
+ describe 'loads yardoc found by' do
32
+ before {
33
+ Yard.expects(:require).at_least(1).with { require 'fileutils'; true }
34
+ Yard.expects(:create_completion_file)
35
+ M.expects(:load_file)
36
+ }
37
+
38
+ it 'registry' do
39
+ YARD::Registry.expects(:yardoc_file_for_gem).returns('/dir/.yardoc')
40
+ load_yard_gems
41
+ end
42
+
43
+ describe 'find_gem_file and' do
44
+ before {
45
+ YARD::Registry.expects(:yardoc_file_for_gem).returns(nil)
46
+ M.expects(:find_gem_file).returns('/dir/blah.rb')
47
+ }
48
+
49
+ it 'prints building message' do
50
+ Yard.expects(:system)
51
+ capture_stdout { load_yard_gems('bond') }.should =~ /Building.*bond's/
52
+ end
53
+
54
+ it "caches yardoc by default" do
55
+ Yard.expects(:system).with {|*args| args.include?('-c') }
56
+ capture_stdout { load_yard_gems }
57
+ end
58
+
59
+ it "doesn't cache yardoc with :reload option" do
60
+ Yard.expects(:system).with {|*args| !args.include?('-c') }
61
+ capture_stdout { load_yard_gems('blah', :reload=>true) }
62
+ end
63
+
64
+ it "prints multiple messages with :verbose option" do
65
+ Yard.expects(:system).with {|*args| !args.include?('-q') }
66
+ capture_stdout { load_yard_gems('blah', :verbose=>true) }.should =~ /yardoc -n/
67
+ end
68
+ end
69
+ end
70
+
71
+ describe 'creates completion file' do
72
+ before {
73
+ Yard.expects(:require).at_least(1).with { require 'fileutils'; true }
74
+ Yard.expects(:find_yardoc).returns('/dir/.yardoc')
75
+ M.expects(:load_file)
76
+ }
77
+
78
+ it "with :reload option" do
79
+ File.expects(:exists?).returns(true)
80
+ Yard.expects(:create_completion_file)
81
+ load_yard_gems 'blah', :reload=>true
82
+ end
83
+
84
+ it "with new completion file" do
85
+ File.expects(:exists?).returns(false)
86
+ Yard.expects(:create_completion_file)
87
+ load_yard_gems
88
+ end
89
+
90
+ describe 'which has' do
91
+ before { YARD::Registry.expects(:load!) }
92
+
93
+ it 'methods' do
94
+ Yard.expects(:find_methods_with_options).returns({"Bond::M.start"=>[':one', 'two']})
95
+ expected_body = %[complete(:method=>'Bond::M.start') {\n ["one", "two"]\n}]
96
+ File.expects(:open).yields mock('block') { expects(:write).with(expected_body) }
97
+ load_yard_gems
98
+ end
99
+
100
+ it 'no methods' do
101
+ Yard.expects(:find_methods_with_options).returns({})
102
+ File.expects(:open).yields mock('block') { expects(:write).with('') }
103
+ load_yard_gems
104
+ end
105
+
106
+ it 'methods that map from #initialize to .new' do
107
+ Yard.expects(:find_methods_with_options).returns({"Bond::Agent#initialize"=>[':one', 'two']})
108
+ expected_body = %[complete(:method=>'Bond::Agent.new') {\n ["one", "two"]\n}]
109
+ File.expects(:open).yields mock('block') { expects(:write).with(expected_body) }
110
+ load_yard_gems
111
+ end
112
+ end
113
+ end
114
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 0
9
- version: 0.2.0
8
+ - 1
9
+ version: 0.2.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Gabriel Horner
@@ -14,27 +14,58 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-07 00:00:00 -04:00
17
+ date: 2010-05-19 00:00:00 -04:00
18
18
  default_executable:
19
- dependencies: []
20
-
21
- description: "Bond is on a mission to improve irb\xE2\x80\x99s autocompletion. Aside from doing everything irb\xE2\x80\x99s can do and fixing its quirks, Bond can autocomplete argument(s) to methods, uniquely completing per module, per method and per argument. Bond brings irb\xE2\x80\x99s completion closer to bash/zsh as it provides a configuration system and a DSL for creating custom completions and completion rules. With this configuration system, users can customize their irb autocompletions and share it with others. Bond is able to offer more than irb\xE2\x80\x99s completion since it uses a Readline C extension to get the full line of input when completing as opposed to irb\xE2\x80\x99s last-word approach."
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bacon
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: mocha
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: mocha-on-bacon
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id003
59
+ description: "Bond is on a mission to improve irb\xE2\x80\x99s autocompletion. Aside from doing everything irb\xE2\x80\x99s can do and fixing its quirks, Bond can autocomplete argument(s) to methods, uniquely completing per module, per method and per argument. Bond brings irb\xE2\x80\x99s completion closer to bash/zsh as it provides a configuration system and a DSL for creating custom completions and completion rules. With this configuration system, users can customize their irb autocompletions and share it with others. Bond can also generate completions from yard documentation and load completions that ship with gems. Bond is able to offer more than irb\xE2\x80\x99s completion since it uses a Readline C extension to get the full line of input when completing as opposed to irb\xE2\x80\x99s last-word approach."
22
60
  email: gabriel.horner@gmail.com
23
61
  executables: []
24
62
 
25
63
  extensions:
26
64
  - ext/readline_line_buffer/extconf.rb
27
65
  extra_rdoc_files:
28
- - LICENSE.txt
29
66
  - README.rdoc
30
- files:
31
- - CHANGELOG.rdoc
32
67
  - LICENSE.txt
33
- - README.rdoc
34
- - Rakefile
35
- - ext/readline_line_buffer/extconf.rb
36
- - ext/readline_line_buffer/readline_line_buffer.c
37
- - lib/bond.rb
68
+ files:
38
69
  - lib/bond/agent.rb
39
70
  - lib/bond/completion.rb
40
71
  - lib/bond/completions/activerecord.rb
@@ -58,6 +89,8 @@ files:
58
89
  - lib/bond/readline.rb
59
90
  - lib/bond/search.rb
60
91
  - lib/bond/version.rb
92
+ - lib/bond/yard.rb
93
+ - lib/bond.rb
61
94
  - test/agent_test.rb
62
95
  - test/anywhere_mission_test.rb
63
96
  - test/bacon_extensions.rb
@@ -70,16 +103,25 @@ files:
70
103
  - test/operator_method_mission_test.rb
71
104
  - test/search_test.rb
72
105
  - test/test_helper.rb
106
+ - test/yard_test.rb
107
+ - LICENSE.txt
108
+ - CHANGELOG.rdoc
109
+ - README.rdoc
110
+ - ext/readline_line_buffer/extconf.rb
111
+ - ext/readline_line_buffer/readline_line_buffer.c
112
+ - Rakefile
113
+ - gemspec
73
114
  has_rdoc: true
74
115
  homepage: http://tagaholic.me/bond/
75
116
  licenses: []
76
117
 
77
118
  post_install_message:
78
- rdoc_options:
79
- - --charset=UTF-8
119
+ rdoc_options: []
120
+
80
121
  require_paths:
81
122
  - lib
82
123
  required_ruby_version: !ruby/object:Gem::Requirement
124
+ none: false
83
125
  requirements:
84
126
  - - ">="
85
127
  - !ruby/object:Gem::Version
@@ -87,29 +129,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
129
  - 0
88
130
  version: "0"
89
131
  required_rubygems_version: !ruby/object:Gem::Requirement
132
+ none: false
90
133
  requirements:
91
134
  - - ">="
92
135
  - !ruby/object:Gem::Version
93
136
  segments:
94
- - 0
95
- version: "0"
137
+ - 1
138
+ - 3
139
+ - 6
140
+ version: 1.3.6
96
141
  requirements: []
97
142
 
98
143
  rubyforge_project: tagaholic
99
- rubygems_version: 1.3.6
144
+ rubygems_version: 1.3.7
100
145
  signing_key:
101
146
  specification_version: 3
102
147
  summary: "Mission: Easy custom autocompletion for arguments, methods and beyond. Accomplished for irb and any other readline-like console environments."
103
- test_files:
104
- - test/agent_test.rb
105
- - test/anywhere_mission_test.rb
106
- - test/bacon_extensions.rb
107
- - test/bond_test.rb
108
- - test/completion_test.rb
109
- - test/completions_test.rb
110
- - test/method_mission_test.rb
111
- - test/mission_test.rb
112
- - test/object_mission_test.rb
113
- - test/operator_method_mission_test.rb
114
- - test/search_test.rb
115
- - test/test_helper.rb
148
+ test_files: []
149
+