bond 0.1.0
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 +2 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +191 -0
- data/Rakefile +77 -0
- data/VERSION.yml +4 -0
- data/ext/readline_line_buffer/extconf.rb +3 -0
- data/ext/readline_line_buffer/readline_line_buffer.c +22 -0
- data/lib/bond.rb +92 -0
- data/lib/bond/agent.rb +44 -0
- data/lib/bond/mission.rb +77 -0
- data/lib/bond/missions/default_mission.rb +11 -0
- data/lib/bond/missions/method_mission.rb +13 -0
- data/lib/bond/missions/object_mission.rb +41 -0
- data/lib/bond/rawline.rb +16 -0
- data/lib/bond/readline.rb +47 -0
- data/lib/bond/search.rb +30 -0
- data/test/agent_test.rb +64 -0
- data/test/bond_test.rb +31 -0
- data/test/mission_test.rb +87 -0
- data/test/test_helper.rb +43 -0
- metadata +78 -0
data/CHANGELOG.rdoc
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2009 Gabriel Horner
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
== Description
|
2
|
+
|
3
|
+
Bond is on a mission to make custom autocompletion easy in irb and other console/readline-like
|
4
|
+
environments. Bond supports custom argument completion of methods, method completion of objects and
|
5
|
+
anything else your wicked regex's can do. Bond comes armed with a Readline C extension to get the
|
6
|
+
full line of input as opposed to irb's last-word based completion. Bond makes custom searching of
|
7
|
+
possible completions easy which allows for nontraditional ways of autocompleting i.e. instant
|
8
|
+
aliasing of multi worded methods.
|
9
|
+
|
10
|
+
== Install
|
11
|
+
|
12
|
+
Install the gem with:
|
13
|
+
|
14
|
+
sudo gem install cldwalker-bond --source http://gems.github.com
|
15
|
+
|
16
|
+
== Usage
|
17
|
+
|
18
|
+
=== Argument Completion for Methods
|
19
|
+
|
20
|
+
bash> irb -rirb/completion -rubygems
|
21
|
+
# This loads Bond but it doesn't take over completion yet.
|
22
|
+
>> require 'bond'
|
23
|
+
|
24
|
+
# For Bond to handle completions, we must explicitly define a completion mission.
|
25
|
+
# Order matters since the order they're declared in is the order they're searched.
|
26
|
+
>> Bond.complete(:method=>'method1') {|input| %w{some args to autocomplete} }
|
27
|
+
=> true
|
28
|
+
>> Bond.complete(:method=>'method2') {|input| %w{more args to autocomplete} }
|
29
|
+
=> true
|
30
|
+
|
31
|
+
# Works as above
|
32
|
+
>> method1 [TAB]
|
33
|
+
args autocomplete some to
|
34
|
+
>> method1 'a[TAB]
|
35
|
+
args autocomplete
|
36
|
+
>> method1 'au[TAB]
|
37
|
+
>> method1 'autocomplete'
|
38
|
+
|
39
|
+
# Anything not matched by the above completion missions defaults to irb completion
|
40
|
+
>> $std[TAB]
|
41
|
+
$stderr $stdin $stdout
|
42
|
+
|
43
|
+
=== File Argument Completion for Methods
|
44
|
+
|
45
|
+
# Pass :search=>false to turn off Bond searching since FILENAME_COMPLETION_PROC does it for us.
|
46
|
+
>> Bond.complete(:method=>"File.read", :search=>false) {|input|
|
47
|
+
Readline::FILENAME_COMPLETION_PROC.call(input) || [] }
|
48
|
+
=> true
|
49
|
+
|
50
|
+
# Test drive it
|
51
|
+
>> File.read '[TAB]
|
52
|
+
.git/ LICENSE.txt README.rdoc Rakefile VERSION.yml bond.gemspec ext/ lib/ test/
|
53
|
+
>> File.read 'l[TAB]
|
54
|
+
>> File.read 'lib/
|
55
|
+
>> File.read 'lib/bond.[TAB]
|
56
|
+
>> File.read 'lib/bond.rb'
|
57
|
+
|
58
|
+
# Since File.read doesn't understand ~, let's improve the above completion proc
|
59
|
+
>> file_completion = proc {|input| (Readline::FILENAME_COMPLETION_PROC.call(input) ||
|
60
|
+
[]).map {|f| f =~ /^~/ ? File.expand_path(f) : f } }
|
61
|
+
=> #< Proc:0x0100f1d0@(irb):20>
|
62
|
+
>> Bond.reset; Bond.complete :method=>"File.read", :search=>false, &file_completion
|
63
|
+
=> true
|
64
|
+
|
65
|
+
# Tilda test driving
|
66
|
+
>> File.read "~/[TAB]
|
67
|
+
>> File.read "/Users/bozo/
|
68
|
+
>> File.read "/Users/bozo/.alias.[TAB]
|
69
|
+
>> File.read "/Users/bozo/.alias.yml"
|
70
|
+
|
71
|
+
# Let's add this to *all* our File methods:
|
72
|
+
>> Bond.reset; Bond.complete :method=>/File\.(#{Regexp.union(*File.methods(false))})/,
|
73
|
+
:search=>false, &file_completion
|
74
|
+
=> true
|
75
|
+
|
76
|
+
=== Method Autocompletion on Specified Objects
|
77
|
+
# Let's explore Bond::Agent's functionality
|
78
|
+
>> ba = Bond.agent; nil
|
79
|
+
=> nil
|
80
|
+
# Irb let's you autocomplete everything that an object responds to for better or worse.
|
81
|
+
>> ba.[TAB]
|
82
|
+
ba.__id__ ba.eql? ba.instance_eval ba.method ba.send ba.to_yaml
|
83
|
+
ba.__send__ ba.equal? ba.instance_of? ba.methods ba.setup ba.to_yaml_properties
|
84
|
+
ba.call ba.extend ba.instance_variable_defined? ba.missions ba.singleton_methods ba.to_yaml_style
|
85
|
+
ba.class ba.find_mission ba.instance_variable_get ba.nil? ba.taguri ba.type
|
86
|
+
ba.clone ba.freeze ba.instance_variable_set ba.object_id ba.taguri= ba.untaint
|
87
|
+
ba.complete ba.frozen? ba.instance_variables ba.private_methods ba.taint
|
88
|
+
ba.default_mission ba.hash ba.is_a? ba.protected_methods ba.tainted?
|
89
|
+
ba.display ba.id ba.kind_of? ba.public_methods ba.to_a
|
90
|
+
ba.dup ba.inspect ba.line_buffer ba.respond_to? ba.to_s
|
91
|
+
|
92
|
+
|
93
|
+
# Since it's hard to see Bond::Agent's functionality amidst all the Object and Kernel methods,
|
94
|
+
# let's autocomplete just it's instance methods.
|
95
|
+
>> Bond.complete(:object=>Bond::Agent) {|input| input.object.class.instance_methods(false) }
|
96
|
+
=> true
|
97
|
+
|
98
|
+
# A less cluttered display of Bond::Agent's functionality.
|
99
|
+
>> ba.[TAB]
|
100
|
+
ba.call ba.complete ba.default_mission ba.find_mission ba.missions
|
101
|
+
|
102
|
+
# Let's have all Bond::* objects do this.
|
103
|
+
>> Bond.reset; Bond.complete(:object=>/^Bond::/) {|input| input.object.class.instance_methods(false) }
|
104
|
+
=> true
|
105
|
+
|
106
|
+
# Let's revert method autocompletion back to irb's defaults for Bond::* objects.
|
107
|
+
>> Bond.reset; Bond.complete :object=>/^Bond::/
|
108
|
+
|
109
|
+
=== Underscore Search
|
110
|
+
|
111
|
+
# Firing up a rails console
|
112
|
+
bash> script/console
|
113
|
+
>> require 'bond'
|
114
|
+
=> true
|
115
|
+
|
116
|
+
# Set all ActiveRecord::Base descendants to use the predefined underscore search
|
117
|
+
>> Bond.complete :object=>ActiveRecord::Base, :search=>:underscore
|
118
|
+
=> true
|
119
|
+
|
120
|
+
# With this search we can still autocomplete the traditional way.
|
121
|
+
# Url is a model object
|
122
|
+
>> Url.first.tag_[TAB]
|
123
|
+
Url.first.tag_add_and_remove Url.first.tag_and_save Url.first.tag_ids= Url.first.tag_list=
|
124
|
+
Url.first.tag_add_and_save Url.first.tag_ids Url.first.tag_list Url.first.tag_remove_and_save
|
125
|
+
>> Url.tag_ad[TAB]
|
126
|
+
>> Url.tag_add_and_
|
127
|
+
>> Url.tag_add_and_[TAB]
|
128
|
+
Url.first.tag_add_and_remove Url.first.tag_add_and_save
|
129
|
+
>> Url.tag_add_and_s[TAB]
|
130
|
+
>> Url.tag_add_and_save
|
131
|
+
|
132
|
+
# But this search goes the extra mile with textmate-like searching.
|
133
|
+
# Type just the first letter of each underscored word separated by '-'
|
134
|
+
>> Url.first.t-a-a-s[TAB]
|
135
|
+
>> Url.first.tag_add_and_save
|
136
|
+
|
137
|
+
# With this search, most multi-worded methods are just a few keystrokes away.
|
138
|
+
# If multiple methods match the underscore alias, it still autocompletes the beginning of the method:
|
139
|
+
>> Url.first.p[TAB]
|
140
|
+
Url.first.partial_updates Url.first.pretty_inspect Url.first.pretty_print_instance_variables Url.first.public_methods
|
141
|
+
Url.first.partial_updates? Url.first.pretty_print Url.first.primary_key_prefix_type
|
142
|
+
Url.first.pluralize_table_names Url.first.pretty_print_cycle Url.first.private_methods
|
143
|
+
Url.first.present? Url.first.pretty_print_inspect Url.first.protected_methods
|
144
|
+
>> Url.first.p-p[TAB]
|
145
|
+
>> Url.first.pretty_print
|
146
|
+
>> Url.first.pretty_print_c[TAB]
|
147
|
+
>> Url.first.pretty_print_cycle
|
148
|
+
|
149
|
+
|
150
|
+
=== Custom AutoCompletion
|
151
|
+
|
152
|
+
bash> irb -rirb/completion -rubygems -rbond
|
153
|
+
# Let's reuse the file completion from above
|
154
|
+
>> file_completion = proc {|input| (Readline::FILENAME_COMPLETION_PROC.call(input) ||
|
155
|
+
[]).map {|f| f =~ /^~/ ? File.expand_path(f) : f } }
|
156
|
+
=> #< Proc:0x0100f1d0@(irb):1>
|
157
|
+
|
158
|
+
# But this time let's trigger it whenever the last word in the line is quoted
|
159
|
+
# fyi this is default behavior if you use irb without requiring irb/completion
|
160
|
+
>> Bond.complete(:on=>/\S+\s*["']([^'".]*)$/, :search=>false) {|input| file_completion.call(input.matched[1]) }
|
161
|
+
=> true
|
162
|
+
|
163
|
+
# Now it doesn't matter what methods come before. If the last word is quoted we get file completion:
|
164
|
+
>> Dir.entries '[TAB]
|
165
|
+
.git/ LICENSE.txt README.rdoc Rakefile VERSION.yml bond.gemspec ext/ lib/ test/
|
166
|
+
>> Dir.entries 'l[TAB]
|
167
|
+
>> Dir.entries 'lib/
|
168
|
+
>> `ls 't[TAB]
|
169
|
+
>> `ls 'test/
|
170
|
+
>> `ls 'test/'`
|
171
|
+
|
172
|
+
# String method completion still works
|
173
|
+
>> '007'.[TAB]
|
174
|
+
Display all 137 possibilities? (y or n)
|
175
|
+
|
176
|
+
== Credits
|
177
|
+
Thanks to Csaba Hank for {providing the C extension}[http://www.creo.hu/~csaba/ruby/irb-enhancements/doc/files/README.html]
|
178
|
+
which Bond uses to read Readline's full buffer. Thanks also goes out to Takao Kouji for {recently
|
179
|
+
commiting}[http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/ext/readline/readline.c?view=diff&r1=24018&r2=24019]
|
180
|
+
this Readline enhancement to ruby.
|
181
|
+
|
182
|
+
== Bugs
|
183
|
+
The underscore search intermittently doesn't complete when it should and deletes the last
|
184
|
+
character typed.
|
185
|
+
|
186
|
+
== 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
|
+
* Bond.spy for easily viewing/debugging what completion mission matches a given input.
|
190
|
+
* Argument autocompletion by object class.
|
191
|
+
* Make replacement of existing completions not require doing a Bond.reset.
|
data/Rakefile
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
begin
|
5
|
+
require 'rcov/rcovtask'
|
6
|
+
|
7
|
+
Rcov::RcovTask.new do |t|
|
8
|
+
t.libs << 'test'
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
10
|
+
t.rcov_opts = ["-T -x '/Library/Ruby/*'"]
|
11
|
+
t.verbose = true
|
12
|
+
end
|
13
|
+
rescue LoadError
|
14
|
+
puts "Rcov not available. Install it for rcov-related tasks with: sudo gem install rcov"
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'jeweler'
|
19
|
+
Jeweler::Tasks.new do |s|
|
20
|
+
s.name = "bond"
|
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 make custom autocompletion easy in irb and other console/readline-like environments. Bond supports custom argument completion of methods, method completion of objects and anything else your wicked regex's can do. Bond comes armed with a Readline C extension to get the full line of input as opposed to irb's last-word based completion. Bond makes custom searching of possible completions easy which allows for nontraditional ways of autocompleting i.e. instant aliasing of multi worded methods."
|
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", "VERSION.yml", "LICENSE.txt", "{bin,lib,test,ext}/**/*"]
|
31
|
+
end
|
32
|
+
|
33
|
+
rescue LoadError
|
34
|
+
puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
35
|
+
end
|
36
|
+
|
37
|
+
Rake::TestTask.new do |t|
|
38
|
+
t.libs << 'lib'
|
39
|
+
t.pattern = 'test/**/*_test.rb'
|
40
|
+
t.verbose = false
|
41
|
+
end
|
42
|
+
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
rdoc.rdoc_dir = 'rdoc'
|
45
|
+
rdoc.title = 'test'
|
46
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
50
|
+
|
51
|
+
# I assume contributors can edit the gemspec for most attributes except for file-related ones.
|
52
|
+
# For those attributes you can use the gemspec_update task. I prefer not to give you the gem-creator-specific rake
|
53
|
+
# tasks I use to generate gemspecs to give you the choice of generating gemspecs however you'd like.
|
54
|
+
# More about this here: http://tagaholic.me/2009/04/08/building-dry-gems-with-thor-and-jeweler.html .
|
55
|
+
desc "Update gemspec from existing one by regenerating path globs specified in *.gemspec.yml or defaults to liberal path globs."
|
56
|
+
task :gemspec_update do
|
57
|
+
if (gemspec_file = Dir['*.gemspec'][0])
|
58
|
+
original_gemspec = eval(File.read(gemspec_file))
|
59
|
+
if File.exists?("#{gemspec_file}.yml")
|
60
|
+
require 'yaml'
|
61
|
+
YAML::load_file("#{gemspec_file}.yml").each do |attribute, globs|
|
62
|
+
original_gemspec.send("#{attribute}=", FileList[globs])
|
63
|
+
end
|
64
|
+
else
|
65
|
+
# liberal defaults
|
66
|
+
original_gemspec.files = FileList["**/*"]
|
67
|
+
test_directories = original_gemspec.test_files.grep(/\//).map {|e| e[/^[^\/]+/]}.compact.uniq
|
68
|
+
original_gemspec.test_files = FileList["{#{test_directories.join(',')}}/**/*"] unless test_directories.empty?
|
69
|
+
end
|
70
|
+
File.open(gemspec_file, 'w') {|f| f.write(original_gemspec.to_ruby) }
|
71
|
+
puts "Updated gemspec."
|
72
|
+
else
|
73
|
+
puts "No existing gemspec file found."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
task :default => :test
|
data/VERSION.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
/* readline.c -- GNU Readline module
|
2
|
+
Copyright (C) 1997-2001 Shugo Maeda */
|
3
|
+
/* body of line_buffer() from irb enhancements at http://www.creo.hu/~csaba/ruby/ */
|
4
|
+
|
5
|
+
#include "ruby.h"
|
6
|
+
#include <errno.h>
|
7
|
+
#include <stdio.h>
|
8
|
+
#include <readline/readline.h>
|
9
|
+
|
10
|
+
static VALUE line_buffer(VALUE self)
|
11
|
+
{
|
12
|
+
rb_secure(4);
|
13
|
+
if (rl_line_buffer == NULL)
|
14
|
+
return Qnil;
|
15
|
+
return rb_tainted_str_new2(rl_line_buffer);
|
16
|
+
}
|
17
|
+
|
18
|
+
void Init_readline_line_buffer() {
|
19
|
+
VALUE c = rb_cObject;
|
20
|
+
c = rb_const_get(c, rb_intern("Readline"));
|
21
|
+
rb_define_singleton_method(c, "line_buffer", (VALUE(*)(ANYARGS))line_buffer, -1);
|
22
|
+
}
|
data/lib/bond.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
current_dir = File.dirname(__FILE__)
|
2
|
+
$:.unshift(current_dir) unless $:.include?(current_dir) || $:.include?(File.expand_path(current_dir))
|
3
|
+
require 'bond/readline'
|
4
|
+
require 'bond/rawline'
|
5
|
+
require 'bond/agent'
|
6
|
+
require 'bond/search'
|
7
|
+
require 'bond/mission'
|
8
|
+
require 'bond/missions/default_mission'
|
9
|
+
require 'bond/missions/method_mission'
|
10
|
+
require 'bond/missions/object_mission'
|
11
|
+
|
12
|
+
# Bond allows easy handling and creation of completion missions/rules with Bond.complete. When Bond is asked to autocomplete, Bond looks
|
13
|
+
# up the completion missions in the order they were defined and picks the first one that matches what the user has typed.
|
14
|
+
# Bond::Agent handles finding and executing the correct completion mission.
|
15
|
+
# Some pointers on using/understanding Bond:
|
16
|
+
# * Bond can work outside of irb and readline when debriefed with Bond.debrief. This should be called before any Bond.complete calls.
|
17
|
+
# * Bond doesn't take over completion until an explicit Bond.complete is called.
|
18
|
+
# * 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.
|
21
|
+
# * If no completion missions match, then Bond falls back on a default mission. If using irb and irb/completion
|
22
|
+
# this falls back on irb's completion. Otherwise an empty completion list is returned.
|
23
|
+
module Bond
|
24
|
+
extend self
|
25
|
+
|
26
|
+
# Defines a completion mission aka a Bond::Mission. A valid mission consists of a condition and an action block.
|
27
|
+
# A condition is specified with one of the following options: :on, :object or :method. Depending on the condition option, a
|
28
|
+
# different type of Bond::Mission is created. Action blocks are given what the user has typed and should a return a list of possible
|
29
|
+
# completions. By default Bond searches possible completions to only return the ones that match what has been typed. This searching
|
30
|
+
# behavior can be customized with the :search option.
|
31
|
+
# ====Options:
|
32
|
+
# [:on] Matches the given regular expression with the full line of input. Creates a Bond::Mission object.
|
33
|
+
# Access to the matches in the regular expression are passed to the completion proc as the input's attribute :matched.
|
34
|
+
# [:method] Matches the given string or regular expression with any methods (or any non-whitespace string) that start the beginning
|
35
|
+
# of a line. Creates a Bond::Missions:MethodMission object. If given a string, the match has to be exact.
|
36
|
+
# Since this is used mainly for argument completion, completions can have an optional quote in front of them.
|
37
|
+
# [:object] Matches the given a string or regular expression to the ancestor of the current object being completed. Creates a
|
38
|
+
# Bond::Missions::ObjectMission object. Access to the current object is passed to the completion proc as the input's
|
39
|
+
# attribute :object. If no action is given, this completion type defaults to all methods the object responds to.
|
40
|
+
# [:search] Given a symbol, proc or false, determines how completions are searched to match what the user has typed. Defaults to
|
41
|
+
# traditional searching i.e. looking at the beginning of a string for possible matches. If false, search is turned off and
|
42
|
+
# assumed to be done in the action block. Possible symbols are :anywhere, :ignore_case and :underscore. See Bond::Search for
|
43
|
+
# more info about them. A proc is given two arguments: the input string and an array of possible completions.
|
44
|
+
#
|
45
|
+
# ==== Examples:
|
46
|
+
# Bond.complete(:method=>'shoot') {|input| %w{to kill} }
|
47
|
+
# Bond.complete(:on=>/^((([a-z][^:.\(]*)+):)+/, :search=>false) {|input| Object.constants.grep(/#{input.matched[1]}/) }
|
48
|
+
# Bond.complete(:object=>ActiveRecord::Base, :search=>:underscore)
|
49
|
+
# Bond.complete(:object=>ActiveRecord::Base) {|input| input.object.class.instance_methods(false) }
|
50
|
+
# Bond.complete(:method=>'you', :search=>proc {|input, list| list.grep(/#{input}/i)} ) {|input| %w{Only Live Twice} }
|
51
|
+
def complete(options={}, &block)
|
52
|
+
agent.complete(options, &block)
|
53
|
+
true
|
54
|
+
rescue InvalidMissionError
|
55
|
+
$stderr.puts "Invalid mission given. Mission needs an action and a condition."
|
56
|
+
false
|
57
|
+
rescue
|
58
|
+
$stderr.puts "Mission setup failed with:", $!
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
# Resets Bond so that next time Bond.complete is called, a new set of completion missions are created. This does not
|
63
|
+
# change current completion behavior.
|
64
|
+
def reset
|
65
|
+
@agent = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Debriefs Bond to set global defaults.
|
69
|
+
# ==== Options:
|
70
|
+
# [:readline_plugin] Specifies a Bond plugin to interface with a Readline-like library. Available plugins are Bond::Readline and Bond::Rawline.
|
71
|
+
# Defaults to Bond::Readline. Note that a plugin doesn't imply use with irb. Irb is joined to the hip with Readline.
|
72
|
+
# [: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
|
+
# enabled, uses irb completion. Otherwise defaults to a proc with an empty completion list.
|
74
|
+
# [:eval_binding] Specifies a binding to be used with Bond::Missions::ObjectMission. When in irb, defaults to irb's main binding. Otherwise
|
75
|
+
# defaults to TOPLEVEL_BINDING.
|
76
|
+
# [:debug] Boolean to print unexpected errors when autocompletion fails. Default is false.
|
77
|
+
def debrief(options={})
|
78
|
+
config.merge! options
|
79
|
+
plugin_methods = %w{setup line_buffer}
|
80
|
+
unless config[:readline_plugin].is_a?(Module) && plugin_methods.all? {|e| config[:readline_plugin].instance_methods.include?(e)}
|
81
|
+
$stderr.puts "Invalid readline plugin set. Try again."
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def agent #:nodoc:
|
86
|
+
@agent ||= Agent.new(config)
|
87
|
+
end
|
88
|
+
|
89
|
+
def config #:nodoc:
|
90
|
+
@config ||= {:readline_plugin=>Bond::Readline, :debug=>false}
|
91
|
+
end
|
92
|
+
end
|
data/lib/bond/agent.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Bond
|
2
|
+
# Handles finding and executing the first mission that matches the input line. Once found, it calls the mission's action.
|
3
|
+
class Agent
|
4
|
+
# The array of missions that will be searched when a completion occurs.
|
5
|
+
attr_reader :missions
|
6
|
+
|
7
|
+
def initialize(options={}) #:nodoc:
|
8
|
+
raise ArgumentError unless options[:readline_plugin].is_a?(Module)
|
9
|
+
extend(options[:readline_plugin])
|
10
|
+
@default_mission_action = options[:default_mission] if options[:default_mission]
|
11
|
+
@eval_binding = options[:eval_binding] if options[:eval_binding]
|
12
|
+
setup
|
13
|
+
@missions = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def complete(options={}, &block) #:nodoc:
|
17
|
+
@missions << Mission.create(options.merge(:action=>block, :eval_binding=>@eval_binding))
|
18
|
+
end
|
19
|
+
|
20
|
+
# This is where the action starts when a completion is initiated.
|
21
|
+
def call(input)
|
22
|
+
(mission = find_mission(input)) ? mission.execute : default_mission.execute(input)
|
23
|
+
rescue FailedExecutionError
|
24
|
+
$stderr.puts "", $!.message
|
25
|
+
rescue
|
26
|
+
if Bond.config[:debug]
|
27
|
+
p $!
|
28
|
+
p $!.backtrace.slice(0,5)
|
29
|
+
end
|
30
|
+
default_mission.execute(input)
|
31
|
+
end
|
32
|
+
|
33
|
+
# No need to use what's passed to the completion proc when we can get the full line.
|
34
|
+
def find_mission(input) #:nodoc:
|
35
|
+
all_input = line_buffer
|
36
|
+
@missions.find {|mission| mission.matches?(all_input) }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Default mission used by agent.
|
40
|
+
def default_mission
|
41
|
+
@default_mission ||= Missions::DefaultMission.new(:action=>@default_mission_action)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/bond/mission.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module Bond
|
2
|
+
# Occurs when a mission is incorrectly defined.
|
3
|
+
class InvalidMissionError < StandardError; end
|
4
|
+
# Occurs when a mission or search action fails.
|
5
|
+
class FailedExecutionError < StandardError; end
|
6
|
+
# Namespace for subclasses of Bond::Mission.
|
7
|
+
class Missions; end
|
8
|
+
|
9
|
+
# A set of conditions and actions to take for a completion scenario or mission in Bond's mind.
|
10
|
+
class Mission
|
11
|
+
include Search
|
12
|
+
|
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)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :action
|
25
|
+
OPERATORS = ["%", "&", "*", "**", "+", "-", "/", "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>", "[]", "[]=", "^"]
|
26
|
+
|
27
|
+
# Options are almost the same as those explained at Bond.complete. The only difference is that the action is passed
|
28
|
+
# as an :action option here.
|
29
|
+
def initialize(options)
|
30
|
+
raise InvalidMissionError unless (options[:action] || respond_to?(:default_action)) &&
|
31
|
+
(options[:on] || is_a?(Missions::DefaultMission))
|
32
|
+
raise InvalidMissionError if options[:on] && !options[:on].is_a?(Regexp)
|
33
|
+
@action = options[:action]
|
34
|
+
@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")
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a boolean indicating if a mission matches the given input.
|
40
|
+
def matches?(input)
|
41
|
+
if (match = handle_valid_match(input))
|
42
|
+
@input.instance_variable_set("@matched", @matched)
|
43
|
+
@input.instance_eval("def self.matched; @matched ; end")
|
44
|
+
end
|
45
|
+
!!match
|
46
|
+
end
|
47
|
+
|
48
|
+
# Called when a mission has been chosen to autocomplete.
|
49
|
+
def execute(*args)
|
50
|
+
if args.empty?
|
51
|
+
list = @action.call(@input) || []
|
52
|
+
list = @search ? @search.call(@input, list) : list
|
53
|
+
@list_prefix ? list.map {|e| @list_prefix + e } : list
|
54
|
+
else
|
55
|
+
@action.call(*args)
|
56
|
+
end
|
57
|
+
rescue
|
58
|
+
error_message = "Mission action failed to execute properly. Check your mission action with pattern #{@condition.inspect}.\n" +
|
59
|
+
"Failed with error: #{$!.message}"
|
60
|
+
raise FailedExecutionError, error_message
|
61
|
+
end
|
62
|
+
|
63
|
+
#:stopdoc:
|
64
|
+
def set_input(input, match)
|
65
|
+
@input = input[/\S+$/]
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_valid_match(input)
|
69
|
+
if (match = input.match(@condition))
|
70
|
+
set_input(input, match)
|
71
|
+
@matched ||= match
|
72
|
+
end
|
73
|
+
match
|
74
|
+
end
|
75
|
+
#:startdoc:
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Represents a default mission which doesn't need an explicit action.
|
2
|
+
class Bond::Missions::DefaultMission < Bond::Mission
|
3
|
+
def initialize(options={}) #:nodoc:
|
4
|
+
options[:action] ||= default_action
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def default_action #:nodoc:
|
9
|
+
Object.const_defined?(:IRB) && IRB.const_defined?(:InputCompletor) ? IRB::InputCompletor::CompletionProc : lambda {|e| [] }
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Represents a completion mission specified by :method in Bond.complete.
|
2
|
+
class Bond::Missions::MethodMission < Bond::Mission
|
3
|
+
def initialize(options={}) #:nodoc:
|
4
|
+
@method = options.delete(:method)
|
5
|
+
@method = Regexp.quote(@method.to_s) unless @method.is_a?(Regexp)
|
6
|
+
options[:on] = /^\s*(#{@method})\s*['"]?(.*)$/
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_input(input, match) #:nodoc:
|
11
|
+
@input = match[-1]
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Represents a completion mission specified by :object in Bond.complete. Unlike other missions, this
|
2
|
+
# one needs to both match the mission condition and have the current object being completed have
|
3
|
+
# an ancestor specified by :object.
|
4
|
+
class Bond::Missions::ObjectMission < Bond::Mission
|
5
|
+
#:stopdoc:
|
6
|
+
def initialize(options={})
|
7
|
+
@object_condition = options.delete(:object)
|
8
|
+
@object_condition = /^#{Regexp.quote(@object_condition.to_s)}$/ unless @object_condition.is_a?(Regexp)
|
9
|
+
options[:on] = /^((\.?[^.]+)+)\.([^.]*)$/
|
10
|
+
@eval_binding = options[:eval_binding] || default_eval_binding
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle_valid_match(input)
|
15
|
+
match = super
|
16
|
+
if match && eval_object(match) && (match = @evaled_object.class.ancestors.any? {|e| e.to_s =~ @object_condition })
|
17
|
+
@list_prefix = @matched[1] + "."
|
18
|
+
@input = @matched[3]
|
19
|
+
@input.instance_variable_set("@object", @evaled_object)
|
20
|
+
@input.instance_eval("def self.object; @object ; end")
|
21
|
+
@action ||= lambda {|e| default_action(e.object) }
|
22
|
+
else
|
23
|
+
match = false
|
24
|
+
end
|
25
|
+
match
|
26
|
+
end
|
27
|
+
|
28
|
+
def eval_object(match)
|
29
|
+
@matched = match
|
30
|
+
@evaled_object = begin eval("#{match[1]}", @eval_binding); rescue Exception; nil end
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_action(obj)
|
34
|
+
obj.methods - OPERATORS
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_eval_binding
|
38
|
+
Object.const_defined?(:IRB) ? IRB.CurrentContext.workspace.binding : ::TOPLEVEL_BINDING
|
39
|
+
end
|
40
|
+
#:startdoc:
|
41
|
+
end
|
data/lib/bond/rawline.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Bond
|
2
|
+
# A readline plugin for use with {Rawline}[http://github.com/h3rald/rawline/tree/master]. This plugin
|
3
|
+
# should be used in conjunction with {a Rawline shell}[http://www.h3rald.com/articles/real-world-rawline-usage].
|
4
|
+
module Rawline
|
5
|
+
def setup
|
6
|
+
require 'rawline'
|
7
|
+
::Rawline.completion_append_character = nil
|
8
|
+
::Rawline.basic_word_break_characters= " \t\n\"\\'`><;|&{("
|
9
|
+
::Rawline.completion_proc = self
|
10
|
+
end
|
11
|
+
|
12
|
+
def line_buffer
|
13
|
+
::Rawline.editor.line.text
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
module Bond
|
3
|
+
# This is the default readline plugin for Bond. A valid plugin must define methods setup() and line_buffer(). setup()
|
4
|
+
# should load the readline-like library and set the completion_proc. line_buffer() should give access to the full line of what
|
5
|
+
# the user has typed.
|
6
|
+
module Readline
|
7
|
+
DefaultBreakCharacters = " \t\n\"\\'`><=;|&{("
|
8
|
+
|
9
|
+
def setup
|
10
|
+
require 'readline'
|
11
|
+
begin
|
12
|
+
require 'readline_line_buffer'
|
13
|
+
rescue LoadError
|
14
|
+
$stderr.puts "Failed to load readline_line_buffer extension. Falling back on RubyInline extension."
|
15
|
+
require 'inline'
|
16
|
+
eval %[
|
17
|
+
module ::Readline
|
18
|
+
inline do |builder|
|
19
|
+
%w(<errno.h> <stdio.h> <readline/readline.h>).each{|h| builder.include h }
|
20
|
+
builder.c_raw_singleton <<-EOC
|
21
|
+
static VALUE line_buffer(VALUE self)
|
22
|
+
{
|
23
|
+
rb_secure(4);
|
24
|
+
if (rl_line_buffer == NULL)
|
25
|
+
return Qnil;
|
26
|
+
return rb_tainted_str_new2(rl_line_buffer);
|
27
|
+
}
|
28
|
+
EOC
|
29
|
+
end
|
30
|
+
end
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Reinforcing irb defaults
|
35
|
+
::Readline.completion_append_character = nil
|
36
|
+
if ::Readline.respond_to?("basic_word_break_characters=")
|
37
|
+
::Readline.basic_word_break_characters = DefaultBreakCharacters
|
38
|
+
end
|
39
|
+
|
40
|
+
::Readline.completion_proc = self
|
41
|
+
end
|
42
|
+
|
43
|
+
def line_buffer
|
44
|
+
::Readline.line_buffer
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/bond/search.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Bond
|
2
|
+
# Contains search methods used to filter possible completions given what the user has typed for that completion.
|
3
|
+
module Search
|
4
|
+
# Searches completions from the beginning of the string.
|
5
|
+
def default_search(input, list)
|
6
|
+
list.grep(/^#{input}/)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Searches completions anywhere in the string.
|
10
|
+
def anywhere_search(input, list)
|
11
|
+
list.grep(/#{input}/)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Searches completions from the beginning and ignores case.
|
15
|
+
def ignore_case_search(input, list)
|
16
|
+
list.grep(/^#{input}/i)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Searches completions from the beginning but also provides aliasing of underscored words.
|
20
|
+
# For example 'some_dang_long_word' can be specified as 's-d-l-w'. Aliases can be any unique string
|
21
|
+
# at the beginning of an underscored word. For example, to choose the first completion between 'so_long' and 'so_larger',
|
22
|
+
# type 's-lo'.
|
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("") =~ /^#{split_input}/ || c =~ /^#{input}/
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/test/agent_test.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class Bond::AgentTest < Test::Unit::TestCase
|
4
|
+
before(:all) {|e| Bond.debrief(:readline_plugin=>valid_readline_plugin) }
|
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
|
+
context "Agent" do
|
26
|
+
before(:each) {|e| Bond.agent.instance_eval("@missions = []") }
|
27
|
+
|
28
|
+
test "chooses default mission if no missions match" do
|
29
|
+
Bond.complete(:on=>/bling/) {|e| [] }
|
30
|
+
Bond.agent.default_mission.expects(:execute)
|
31
|
+
complete 'blah'
|
32
|
+
end
|
33
|
+
|
34
|
+
test "chooses default mission if internal processing fails" do
|
35
|
+
Bond.complete(:on=>/bling/) {|e| [] }
|
36
|
+
Bond.agent.expects(:find_mission).raises
|
37
|
+
Bond.agent.default_mission.expects(:execute)
|
38
|
+
complete('bling')
|
39
|
+
end
|
40
|
+
|
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
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# TODO
|
48
|
+
# test "default_mission set to a valid mission if irb doesn't exist" do
|
49
|
+
# old_default_action = Bond.agent.default_mission.action
|
50
|
+
# Bond.agent.instance_eval("@default_mission = @default_mission_action = nil")
|
51
|
+
# Object.expects(:const_defined?).with(:IRB).returns(false)
|
52
|
+
# Bond.agent.default_mission.is_a?(Bond::Mission).should == true
|
53
|
+
# Bond.agent.default_mission.action.respond_to?(:call).should == true
|
54
|
+
# Bond.agent.default_mission.instance_variable_set(:@action, old_default_action)
|
55
|
+
# end
|
56
|
+
|
57
|
+
# test "binding set to toplevel binding if irb doesn't exist" do
|
58
|
+
# old_binding = Bond.agent.eval_binding
|
59
|
+
# Object.expects(:const_defined?).with(:IRB).returns(false)
|
60
|
+
# Bond.agent.instance_eval("@eval_binding = nil")
|
61
|
+
# Bond.agent.eval_binding.should == ::TOPLEVEL_BINDING
|
62
|
+
# Bond.agent.instance_variable_set(:@eval_binding, old_binding)
|
63
|
+
# end
|
64
|
+
end
|
data/test/bond_test.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class BondTest < Test::Unit::TestCase
|
4
|
+
context "debrief" do
|
5
|
+
before(:each) {|e| Bond.instance_eval("@agent = @config = nil")}
|
6
|
+
test "prints error if readline_plugin is not a module" do
|
7
|
+
capture_stderr { Bond.debrief :readline_plugin=>false }.should =~ /Invalid/
|
8
|
+
end
|
9
|
+
|
10
|
+
test "prints error if readline_plugin doesn't have all required methods" do
|
11
|
+
capture_stderr {Bond.debrief :readline_plugin=>Module.new{ def setup; end } }.should =~ /Invalid/
|
12
|
+
end
|
13
|
+
|
14
|
+
test "no error if valid readline_plugin" do
|
15
|
+
capture_stderr {Bond.debrief :readline_plugin=>valid_readline_plugin }.should == ''
|
16
|
+
end
|
17
|
+
|
18
|
+
test "sets default mission" do
|
19
|
+
default_mission = lambda {}
|
20
|
+
Bond.debrief :default_mission=>default_mission, :readline_plugin=>valid_readline_plugin
|
21
|
+
Bond.agent.default_mission.action.should == default_mission
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
test "reset clears existing missions" do
|
26
|
+
Bond.complete(:on=>/blah/) {[]}
|
27
|
+
Bond.agent.missions.size.should_not == 0
|
28
|
+
Bond.reset
|
29
|
+
Bond.agent.missions.size.should == 0
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class Bond::MissionTest < Test::Unit::TestCase
|
4
|
+
before(:all) {|e| Bond.debrief(:readline_plugin=>valid_readline_plugin) }
|
5
|
+
|
6
|
+
context "mission" do
|
7
|
+
before(:each) {|e| Bond.agent.instance_eval("@missions = []") }
|
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', 'f').should == %w{fg}
|
12
|
+
end
|
13
|
+
|
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', 'c').should == %w{cd}
|
18
|
+
end
|
19
|
+
|
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', 'a').should == %w{ab ad}
|
24
|
+
end
|
25
|
+
|
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', 'c').should == %w{cd}
|
30
|
+
end
|
31
|
+
|
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}
|
36
|
+
end
|
37
|
+
|
38
|
+
test "with no search option and matching completes" do
|
39
|
+
Bond.complete(:on=>/\s*'([^']+)$/, :search=>false) {|e| %w{coco for puffs}.grep(/#{e.matched[1]}/) }
|
40
|
+
complete("require 'ff").should == ['puffs']
|
41
|
+
end
|
42
|
+
|
43
|
+
test "with search proc completes" do
|
44
|
+
Bond.complete(:method=>'blah', :search=>proc {|input, list| list.grep(/#{input}/)}) {|e| %w{coco for puffs} }
|
45
|
+
complete("blah 'ff").should == ['puffs']
|
46
|
+
end
|
47
|
+
|
48
|
+
test "with anywhere search completes" do
|
49
|
+
Bond.complete(:method=>'blah', :search=>:anywhere) {|e| %w{coco for puffs} }
|
50
|
+
complete("blah 'ff").should == ['puffs']
|
51
|
+
end
|
52
|
+
|
53
|
+
test "with ignore case search completes" do
|
54
|
+
Bond.complete(:method=>'blah', :search=>:ignore_case) {|e| %w{Coco For PufFs} }
|
55
|
+
complete("blah 'pu").should == ['PufFs']
|
56
|
+
end
|
57
|
+
|
58
|
+
test "with underscore search completes" do
|
59
|
+
Bond.complete(:on=>/blah/, :search=>:underscore) {|e| %w{and_one big_two can_three} }
|
60
|
+
complete("blah and").should == ['and_one']
|
61
|
+
complete("blah b-t").should == ['big_two']
|
62
|
+
end
|
63
|
+
|
64
|
+
test "with object and default action completes" do
|
65
|
+
Bond.complete(:object=>"String")
|
66
|
+
Bond.complete(:on=>/man/) { %w{upper upster upful}}
|
67
|
+
complete("'man'.u").should == ["'man'.upcase!", "'man'.unpack", "'man'.untaint", "'man'.upcase", "'man'.upto"]
|
68
|
+
end
|
69
|
+
|
70
|
+
test "with regex object completes" do
|
71
|
+
Bond.complete(:object=>/Str/) {|e| e.object.class.superclass.instance_methods(true) }
|
72
|
+
Bond.complete(:on=>/man/) { %w{upper upster upful}}
|
73
|
+
complete("'man'.u").should == ["'man'.untaint"]
|
74
|
+
end
|
75
|
+
|
76
|
+
test "with object and explicit action completes" do
|
77
|
+
Bond.complete(:object=>"String") {|e| e.object.class.superclass.instance_methods(true) }
|
78
|
+
Bond.complete(:on=>/man/) { %w{upper upster upful}}
|
79
|
+
complete("'man'.u").should == ["'man'.untaint"]
|
80
|
+
end
|
81
|
+
|
82
|
+
test "ignores invalid objects" do
|
83
|
+
Bond.complete(:object=>"String")
|
84
|
+
complete("blah.upt").should == []
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'context' #gem install jeremymcanally-context -s http://gems.github.com
|
4
|
+
require 'matchy' #gem install jeremymcanally-matchy -s http://gems.github.com
|
5
|
+
require 'mocha'
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
require 'bond'
|
8
|
+
|
9
|
+
class Test::Unit::TestCase
|
10
|
+
before(:all) {
|
11
|
+
# Mock irb
|
12
|
+
unless Object.const_defined?(:IRB)
|
13
|
+
eval %[
|
14
|
+
module ::IRB
|
15
|
+
class<<self; attr_accessor :CurrentContext; end
|
16
|
+
module InputCompletor; CompletionProc = lambda {|e| [] }; end
|
17
|
+
end
|
18
|
+
]
|
19
|
+
::IRB.CurrentContext = stub(:workspace=>stub(:binding=>binding))
|
20
|
+
end
|
21
|
+
}
|
22
|
+
|
23
|
+
def capture_stderr(&block)
|
24
|
+
original_stderr = $stderr
|
25
|
+
$stderr = fake = StringIO.new
|
26
|
+
begin
|
27
|
+
yield
|
28
|
+
ensure
|
29
|
+
$stderr = original_stderr
|
30
|
+
end
|
31
|
+
fake.string
|
32
|
+
end
|
33
|
+
|
34
|
+
def complete(full_line, last_word=full_line)
|
35
|
+
Bond.agent.stubs(:line_buffer).returns(full_line)
|
36
|
+
Bond.agent.call(last_word)
|
37
|
+
end
|
38
|
+
|
39
|
+
def valid_readline_plugin
|
40
|
+
Module.new{ def setup; end; def line_buffer; end }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bond
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabriel Horner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-16 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Bond is on a mission to make custom autocompletion easy in irb and other console/readline-like environments. Bond supports custom argument completion of methods, method completion of objects and anything else your wicked regex's can do. Bond comes armed with a Readline C extension to get the full line of input as opposed to irb's last-word based completion. Bond makes custom searching of possible completions easy which allows for nontraditional ways of autocompleting i.e. instant aliasing of multi worded methods.
|
17
|
+
email: gabriel.horner@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions:
|
21
|
+
- ext/readline_line_buffer/extconf.rb
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE.txt
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- CHANGELOG.rdoc
|
27
|
+
- LICENSE.txt
|
28
|
+
- README.rdoc
|
29
|
+
- Rakefile
|
30
|
+
- VERSION.yml
|
31
|
+
- ext/readline_line_buffer/extconf.rb
|
32
|
+
- ext/readline_line_buffer/readline_line_buffer.c
|
33
|
+
- lib/bond.rb
|
34
|
+
- lib/bond/agent.rb
|
35
|
+
- lib/bond/mission.rb
|
36
|
+
- lib/bond/missions/default_mission.rb
|
37
|
+
- lib/bond/missions/method_mission.rb
|
38
|
+
- lib/bond/missions/object_mission.rb
|
39
|
+
- lib/bond/rawline.rb
|
40
|
+
- lib/bond/readline.rb
|
41
|
+
- lib/bond/search.rb
|
42
|
+
- test/agent_test.rb
|
43
|
+
- test/bond_test.rb
|
44
|
+
- test/mission_test.rb
|
45
|
+
- test/test_helper.rb
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://tagaholic.me/bond/
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --charset=UTF-8
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project: tagaholic
|
70
|
+
rubygems_version: 1.3.2
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: "Mission: Easy custom autocompletion for arguments, methods and beyond. Accomplished for irb and any other readline-like console environments."
|
74
|
+
test_files:
|
75
|
+
- test/agent_test.rb
|
76
|
+
- test/bond_test.rb
|
77
|
+
- test/mission_test.rb
|
78
|
+
- test/test_helper.rb
|