bond 0.4.2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gemspec +28 -0
  2. data/.travis.yml +8 -0
  3. data/CHANGELOG.rdoc +91 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.rdoc +242 -0
  6. data/Rakefile +47 -0
  7. data/lib/bond.rb +127 -0
  8. data/lib/bond/agent.rb +108 -0
  9. data/lib/bond/completion.rb +16 -0
  10. data/lib/bond/completions/activerecord.rb +12 -0
  11. data/lib/bond/completions/array.rb +1 -0
  12. data/lib/bond/completions/bond.rb +6 -0
  13. data/lib/bond/completions/hash.rb +3 -0
  14. data/lib/bond/completions/kernel.rb +15 -0
  15. data/lib/bond/completions/module.rb +10 -0
  16. data/lib/bond/completions/object.rb +21 -0
  17. data/lib/bond/completions/struct.rb +1 -0
  18. data/lib/bond/input.rb +28 -0
  19. data/lib/bond/m.rb +146 -0
  20. data/lib/bond/mission.rb +151 -0
  21. data/lib/bond/missions/anywhere_mission.rb +15 -0
  22. data/lib/bond/missions/default_mission.rb +21 -0
  23. data/lib/bond/missions/method_mission.rb +197 -0
  24. data/lib/bond/missions/object_mission.rb +44 -0
  25. data/lib/bond/missions/operator_method_mission.rb +27 -0
  26. data/lib/bond/rc.rb +48 -0
  27. data/lib/bond/readline.rb +38 -0
  28. data/lib/bond/readlines/jruby.rb +13 -0
  29. data/lib/bond/readlines/rawline.rb +15 -0
  30. data/lib/bond/readlines/ruby.rb +9 -0
  31. data/lib/bond/search.rb +74 -0
  32. data/lib/bond/version.rb +3 -0
  33. data/test/agent_test.rb +235 -0
  34. data/test/anywhere_mission_test.rb +34 -0
  35. data/test/bond_test.rb +141 -0
  36. data/test/completion_test.rb +148 -0
  37. data/test/completions_test.rb +98 -0
  38. data/test/deps.rip +4 -0
  39. data/test/m_test.rb +34 -0
  40. data/test/method_mission_test.rb +246 -0
  41. data/test/mission_test.rb +51 -0
  42. data/test/object_mission_test.rb +59 -0
  43. data/test/operator_method_mission_test.rb +66 -0
  44. data/test/search_test.rb +140 -0
  45. data/test/test_helper.rb +69 -0
  46. metadata +167 -0
@@ -0,0 +1,15 @@
1
+ # A mission which completes anywhere i.e. even after non word break characters
2
+ # such as '[' or '}'. With options :prefix and :anywhere, this mission matches
3
+ # on the following regexp condition /:prefix?(:anywhere)$/ and passes the first
4
+ # capture group to the mission action.
5
+ class Bond::AnywhereMission < Bond::Mission
6
+ def initialize(options={}) #@private
7
+ options[:on] = Regexp.new("#{options[:prefix]}(#{options[:anywhere]})$")
8
+ super
9
+ end
10
+
11
+ def after_match(input) #@private
12
+ @completion_prefix = input.to_s.sub(/#{Regexp.escape(@matched[1])}$/, '')
13
+ create_input @matched[1]
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # This is the mission called when none of the others match.
2
+ class Bond::DefaultMission < Bond::Mission
3
+ ReservedWords = [
4
+ "BEGIN", "END", "alias", "and", "begin", "break", "case", "class", "def", "defined", "do", "else", "elsif", "end", "ensure",
5
+ "false", "for", "if", "in", "module", "next", "nil", "not", "or", "redo", "rescue", "retry", "return", "self", "super",
6
+ "then", "true", "undef", "unless", "until", "when", "while", "yield"
7
+ ]
8
+
9
+
10
+ # Default action which generates methods, private methods, reserved words, local variables and constants.
11
+ def self.completions(input=nil)
12
+ Bond::Mission.current_eval("methods | private_methods | local_variables | " +
13
+ "self.class.constants | instance_variables") | ReservedWords
14
+ end
15
+
16
+ def initialize(options={}) #@private
17
+ options[:action] ||= self.class.method(:completions)
18
+ super
19
+ end
20
+ def default_on; end #@private
21
+ end
@@ -0,0 +1,197 @@
1
+ module Bond
2
+ # A mission which completes arguments for any module/class method that isn't an operator method.
3
+ # To create this mission or OperatorMethodMission, :method or :methods must be passed to Bond.complete.
4
+ # A completion for a given module/class effects any object that has it as an ancestor. If an object
5
+ # has two ancestors that have completions for the same method, the ancestor closer to the object is
6
+ # picked. For example, if Array#collect and Enumerable#collect have completions, argument completion on
7
+ # '[].collect ' would use Array#collect.
8
+ #
9
+ # ==== Bond.complete Options:
10
+ # [:action] If a string, value is assumed to be a :method and that method's action is copied.
11
+ # Otherwise defaults to normal :action behavior.
12
+ # [:search] If :action is a :method string, defaults to copying its search.
13
+ # Otherwise defaults to normal :search behavior.
14
+ # [:name, :place] These options aren't supported by a MethodMission/OperatorMethodMission completion.
15
+ # ==== Examples:
16
+ # Bond.complete(:methods => %w{delete index rindex}, :class => "Array#") {|e| e.object }
17
+ # Bond.complete(:method => "Hash#index") {|e| e.object.values }
18
+ #
19
+ # ==== Argument Format
20
+ # All method arguments can autocomplete as symbols or strings and the first argument can be prefixed
21
+ # with '(':
22
+ # >> Bond.complete(:method => 'example') { %w{some example eh} }
23
+ # => true
24
+ # >> example '[TAB]
25
+ # eh example some
26
+ # >> example :[TAB]
27
+ # :eh :example :some
28
+ #
29
+ # >> example("[TAB]
30
+ # eh example some
31
+ #
32
+ # ==== Multiple Arguments
33
+ # Every time a comma appears after a method, Bond starts a new completion. This allows a method to
34
+ # complete multiple arguments as well as complete keys for a hash. *Each* argument can be have a unique
35
+ # set of completions since a completion action is aware of what argument it is currently completing:
36
+ # >> Bond.complete(:method => 'FileUtils.chown') {|e|
37
+ # e.argument > 3 ? %w{noop verbose} : %w{root admin me} }
38
+ # => true
39
+ # >> FileUtils.chown 'r[TAB]
40
+ # >> FileUtils.chown 'root'
41
+ # >> FileUtils.chown 'root', 'a[TAB]
42
+ # >> FileUtils.chown 'root', 'admin'
43
+ # >> FileUtils.chown 'root', 'admin', 'some_file', :v[TAB]
44
+ # >> FileUtils.chown 'root', 'admin', 'some_file', :verbose
45
+ # >> FileUtils.chown 'root', 'admin', 'some_file', :verbose => true
46
+ #
47
+ # ==== Developer Notes
48
+ # Unlike other missions, creating these missions with Bond.complete doesn't add more completion rules
49
+ # for an Agent to look through. Instead, all :method(s) completions are handled by one MethodMission
50
+ # object which looks them up with its own hashes. In the same way, all operator methods are
51
+ # handled by one OperatorMethodMission object.
52
+ class MethodMission < Bond::Mission
53
+ class<<self
54
+ # Hash of instance method completions which maps methods to hashes of modules to arrays ([action, search])
55
+ attr_accessor :actions
56
+ # Same as :actions but for class methods
57
+ attr_accessor :class_actions
58
+ # Stores last search result from MethodMission.find
59
+ attr_accessor :last_find
60
+ # Stores class from last search in MethodMission.find
61
+ attr_accessor :last_class
62
+
63
+ # Creates a method action given the same options as Bond.complete
64
+ def create(options)
65
+ if options[:action].is_a?(String)
66
+ klass, klass_meth = split_method(options[:action])
67
+ if (arr = (current_actions(options[:action])[klass_meth] || {})[klass])
68
+ options[:action], options[:search] = [arr[0], options[:search] || arr[1]]
69
+ else
70
+ raise InvalidMissionError, "string :action"
71
+ end
72
+ end
73
+
74
+ raise InvalidMissionError, "array :method" if options[:method].is_a?(Array)
75
+ meths = options[:methods] || Array(options[:method])
76
+ raise InvalidMissionError, "non-string :method(s)" unless meths.all? {|e| e.is_a?(String) }
77
+ if options[:class].is_a?(String)
78
+ options[:class] << '#' unless options[:class][/[#.]$/]
79
+ meths.map! {|e| options[:class] + e }
80
+ end
81
+
82
+ meths.each {|meth|
83
+ klass, klass_meth = split_method(meth)
84
+ (current_actions(meth)[klass_meth] ||= {})[klass] = [options[:action], options[:search]].compact
85
+ }
86
+ nil
87
+ end
88
+
89
+ # Resets all instance and class method actions.
90
+ def reset
91
+ @actions = {}
92
+ @class_actions = {}
93
+ end
94
+
95
+ # Lists method names
96
+ def action_methods
97
+ (actions.keys + class_actions.keys).uniq
98
+ end
99
+
100
+ # Lists full method names, prefixed with class/module
101
+ def all_methods
102
+ (class_actions.map {|m,h| h.map {|k,v| "#{k}.#{m}" } } +
103
+ actions.map {|m,h| h.map {|k,v| "#{k}##{m}" } }).flatten.sort
104
+ end
105
+
106
+ # Returns the first completion by looking up the object's ancestors and finding the closest
107
+ # one that has a completion definition for the given method. Completion is returned
108
+ # as an array containing action proc and optional search to go with it.
109
+ def find(obj, meth)
110
+ last_find = find_with(obj, meth, :<=, @class_actions) if obj.is_a?(Module)
111
+ last_find = find_with(obj, meth, :is_a?, @actions) unless last_find
112
+ @last_class = last_find.is_a?(Array) ? last_find[0] : nil
113
+ @last_find = last_find ? last_find[1] : last_find
114
+ end
115
+
116
+ # Returns a constant like Module#const_get no matter what namespace it's nested in.
117
+ # Returns nil if the constant is not found.
118
+ def any_const_get(name)
119
+ return name if name.is_a?(Module)
120
+ klass = Object
121
+ name.split('::').each {|e| klass = klass.const_get(e) }
122
+ klass
123
+ rescue
124
+ nil
125
+ end
126
+
127
+ protected
128
+ def current_actions(meth)
129
+ meth.include?('.') ? @class_actions : @actions
130
+ end
131
+
132
+ def split_method(meth)
133
+ meth = "Kernel##{meth}" if !meth.to_s[/[.#]/]
134
+ meth.split(/[.#]/,2)
135
+ end
136
+
137
+ def find_with(obj, meth, find_meth, actions)
138
+ (actions[meth] || {}).select {|k,v| get_class(k) }.
139
+ sort {|a,b| get_class(a[0]) <=> get_class(b[0]) || -1 }.
140
+ find {|k,v| obj.send(find_meth, get_class(k)) }
141
+ end
142
+
143
+ def get_class(klass)
144
+ (@klasses ||= {})[klass] ||= any_const_get(klass)
145
+ end
146
+ end
147
+
148
+ self.reset
149
+ OBJECTS = Mission::OBJECTS + %w{\S*?}
150
+ CONDITION = %q{(OBJECTS)\.?(METHODS)(?:\s+|\()(['":])?(.*)$}
151
+
152
+ def match_message #@private
153
+ "Matches completion for method '#{@meth}' in '#{MethodMission.last_class}'."
154
+ end
155
+
156
+ protected
157
+ def do_match(input)
158
+ (@on = default_on) && super && eval_object(@matched[1] ? @matched[1] : 'self') &&
159
+ MethodMission.find(@evaled_object, @meth = matched_method)
160
+ end
161
+
162
+ def default_on
163
+ Regexp.new condition_with_objects.sub('METHODS',Regexp.union(*current_methods).to_s)
164
+ end
165
+
166
+ def current_methods
167
+ self.class.action_methods - OPERATORS
168
+ end
169
+
170
+ def default_action
171
+ MethodMission.last_find[0]
172
+ end
173
+
174
+ def matched_method
175
+ @matched[2]
176
+ end
177
+
178
+ def set_action_and_search
179
+ @action = default_action
180
+ @search = MethodMission.last_find[1] || Search.default_search
181
+ end
182
+
183
+ def after_match(input)
184
+ set_action_and_search
185
+ @completion_prefix, typed = @matched[3], @matched[-1]
186
+ input_options = {:object => @evaled_object, :argument => 1+typed.count(','),
187
+ :arguments => (@completion_prefix.to_s+typed).split(/\s*,\s*/) }
188
+ if typed.to_s.include?(',') && (match = typed.match(/(.*?\s*)([^,]*)$/))
189
+ typed = match[2]
190
+ typed.sub!(/^(['":])/,'')
191
+ @completion_prefix = typed.empty? ? '' : "#{@matched[3]}#{match[1]}#{$1}"
192
+ end
193
+ create_input typed, input_options
194
+ end
195
+
196
+ end
197
+ end
@@ -0,0 +1,44 @@
1
+ # A mission which completes an object's methods. For this mission to match, the
2
+ # condition must match and the current object must have an ancestor that matches
3
+ # :object. Note: To access to the current object being completed on within an
4
+ # action, use the input's object attribute.
5
+ #
6
+ # ==== Bond.complete Options:
7
+ # [:action] If an action is not specified, the default action is to complete an
8
+ # object's non-operator methods.
9
+ #
10
+ # ===== Example:
11
+ # Bond.complete(:object => 'ActiveRecord::Base') {|input| input.object.class.instance_methods(false) }
12
+ class Bond::ObjectMission < Bond::Mission
13
+ OBJECTS = %w<\S+> + Bond::Mission::OBJECTS
14
+ CONDITION = '(OBJECTS)\.(\w*(?:\?|!)?)$'
15
+ def initialize(options={}) #@private
16
+ @object_condition = /^#{options[:object]}$/
17
+ options[:on] ||= Regexp.new condition_with_objects
18
+ super
19
+ end
20
+
21
+ def match_message #@private
22
+ "Matches completion for object with ancestor matching #{@object_condition.inspect}."
23
+ end
24
+
25
+ protected
26
+ def unique_id
27
+ "#{@object_condition.inspect}+#{@on.inspect}"
28
+ end
29
+
30
+ def do_match(input)
31
+ super && eval_object(@matched[1]) && @evaled_object.class.respond_to?(:ancestors) &&
32
+ @evaled_object.class.ancestors.any? {|e| e.to_s =~ @object_condition }
33
+ end
34
+
35
+ def after_match(input)
36
+ @completion_prefix = @matched[1] + "."
37
+ @action ||= lambda {|e| default_action(e.object) }
38
+ create_input @matched[2], :object => @evaled_object
39
+ end
40
+
41
+ def default_action(obj)
42
+ obj.methods.map {|e| e.to_s} - OPERATORS
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ module Bond
2
+ # A mission which completes arguments for any module/class method that is an
3
+ # operator i.e. '>' or '*'. Takes same Bond.complete options as
4
+ # MethodMission. The only operator method this mission doesn't complete is
5
+ # '[]='. The operator '[]' should cover the first argument completion of '[]='
6
+ # anyways.
7
+ class OperatorMethodMission < MethodMission
8
+ OPERATORS = Mission::OPERATORS - ["[]", "[]="]
9
+ OBJECTS = Mission::OBJECTS + %w{\S+}
10
+ CONDITION = %q{(OBJECTS)\s*(METHODS)\s*(['":])?(.*)$}
11
+
12
+ protected
13
+ def current_methods
14
+ (OPERATORS & MethodMission.action_methods) + ['[']
15
+ end
16
+
17
+ def matched_method
18
+ {'['=>'[]'}[@matched[2]] || @matched[2]
19
+ end
20
+
21
+ def after_match(input)
22
+ set_action_and_search
23
+ @completion_prefix, typed = input.to_s.sub(/#{Regexp.quote(@matched[-1])}$/, ''), @matched[-1]
24
+ create_input typed, :object => @evaled_object, :argument => 1
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,48 @@
1
+ module Bond
2
+ # Namespace in which completion files, ~/.bondrc and ~/.bond/completions/*.rb, are evaluated. Methods in this module
3
+ # and Search are the DSL in completion files and can be used within completion actions.
4
+ #
5
+ # === Example ~/.bondrc
6
+ # # complete arguments for any object's :respond_to?
7
+ # complete(:method => "Object#respond_to?") {|e| e.object.methods }
8
+ # # complete arguments for any module's :public
9
+ # complete(:method => "Module#public") {|e| e.object.instance_methods }
10
+ #
11
+ # # Share generate_tags action across completions
12
+ # complete(:method => "edit_tags", :action => :generate_tags)
13
+ # complete(:method => "delete_tags", :search => false) {|e| generate_tags(e).grep(/#{e}/i) }
14
+ #
15
+ # def generate_tags(input)
16
+ # ...
17
+ # end
18
+ module Rc
19
+ extend self, Search
20
+
21
+ # See {Bond#complete}
22
+ def complete(*args, &block); M.complete(*args, &block); end
23
+ # See {Bond#recomplete}
24
+ def recomplete(*args, &block); M.recomplete(*args, &block); end
25
+
26
+ # Action method with search which returns array of files that match current input.
27
+ def files(input)
28
+ (::Readline::FILENAME_COMPLETION_PROC.call(input) || []).map {|f|
29
+ f =~ /^~/ ? File.expand_path(f) : f
30
+ }
31
+ end
32
+
33
+ # Helper method which returns objects of a given class.
34
+ def objects_of(klass)
35
+ object = []
36
+ ObjectSpace.each_object(klass) {|e| object.push(e) }
37
+ object
38
+ end
39
+
40
+ # Calls eval with Mission.current_eval, rescuing any exceptions to return nil.
41
+ # If Bond.config[:debug] is true, exceptions are raised again.
42
+ def eval(str)
43
+ Mission.current_eval(str)
44
+ rescue Exception
45
+ raise if Bond.config[:debug]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,38 @@
1
+ # This is the default readline plugin for Bond. A valid plugin must be an object
2
+ # that responds to methods setup and line_buffer as described below.
3
+ class Bond::Readline
4
+ DefaultBreakCharacters = " \t\n\"\\'`><=;|&{("
5
+
6
+ # Loads the readline-like library and sets the completion_proc to the given agent.
7
+ def self.setup(agent)
8
+ readline_setup
9
+
10
+ # Reinforcing irb defaults
11
+ Readline.completion_append_character = nil
12
+ if Readline.respond_to?("basic_word_break_characters=")
13
+ Readline.basic_word_break_characters = DefaultBreakCharacters
14
+ end
15
+
16
+ Readline.completion_proc = agent
17
+ end
18
+
19
+ def self.readline_setup
20
+ require 'readline'
21
+ load_extension unless Readline.respond_to?(:line_buffer)
22
+ if (Readline::VERSION rescue nil).to_s[/editline/i]
23
+ puts "Bond has detected EditLine and may not work with it." +
24
+ " See the README's Limitations section."
25
+ end
26
+ end
27
+
28
+ def self.load_extension
29
+ require 'readline_line_buffer'
30
+ rescue LoadError
31
+ $stderr.puts "Bond Error: Failed to load readline_line_buffer. Ensure that it exists and was built correctly."
32
+ end
33
+
34
+ # Returns full line of what the user has typed.
35
+ def self.line_buffer
36
+ Readline.line_buffer
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ # Readline for Jruby
2
+ class Bond::Jruby < Bond::Readline
3
+ def self.readline_setup
4
+ require 'readline'
5
+ require 'jruby'
6
+ class << Readline
7
+ ReadlineExt = org.jruby.ext.Readline
8
+ def line_buffer
9
+ ReadlineExt.s_get_line_buffer(JRuby.runtime.current_context, JRuby.reference(self))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # A pure ruby readline which requires {rawline}[http://github.com/h3rald/rawline].
2
+ class Bond::Rawline < Bond::Readline
3
+ def self.setup(agent)
4
+ require 'rawline'
5
+ Rawline.completion_append_character = nil
6
+ Rawline.basic_word_break_characters= " \t\n\"\\'`><;|&{("
7
+ Rawline.completion_proc = agent
8
+ rescue LoadError
9
+ abort "Bond Error: rawline gem is required for this readline plugin -> gem install rawline"
10
+ end
11
+
12
+ def self.line_buffer
13
+ Rawline.editor.line.text
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # A pure ruby readline which requires {rb-readline}[https://github.com/luislavena/rb-readline].
2
+ class Bond::Ruby < Bond::Readline
3
+ def self.readline_setup
4
+ require 'rb-readline'
5
+ rescue LoadError
6
+ abort "Bond Error: rb-readline gem is required for this readline plugin" +
7
+ " -> gem install rb-readline"
8
+ end
9
+ end
@@ -0,0 +1,74 @@
1
+ module Bond
2
+ # Contains search methods used to filter possible completions given what the user has typed for that completion.
3
+ # For a search method to be used by Bond.complete it must end in '_search' and take two arguments: the Input
4
+ # string and an array of possible completions.
5
+ #
6
+ # ==== Creating a search method
7
+ # Say you want to create a custom search which ignores completions containing '-'.
8
+ # In a completion file under Rc namespace, define this method:
9
+ # def ignore_hyphen_search(input, list)
10
+ # normal_search(input, list.select {|e| e !~ /-/ })
11
+ # end
12
+ #
13
+ # Now you can pass this custom search to any complete() as :search => :ignore_hyphen
14
+ module Search
15
+ class<<self
16
+ # Default search used across missions, set by Bond.config[:default_search]
17
+ attr_accessor :default_search
18
+ end
19
+
20
+ # Searches completions from the beginning of the string.
21
+ def normal_search(input, list)
22
+ list.grep(/^#{Regexp.escape(input)}/)
23
+ end
24
+
25
+ # Searches completions anywhere in the string.
26
+ def anywhere_search(input, list)
27
+ list.grep(/#{Regexp.escape(input)}/)
28
+ end
29
+
30
+ # Searches completions from the beginning and ignores case.
31
+ def ignore_case_search(input, list)
32
+ list.grep(/^#{Regexp.escape(input)}/i)
33
+ end
34
+
35
+ # A normal_search which also provides aliasing of underscored words.
36
+ # For example 'some_dang_long_word' can be specified as 's_d_l_w'. Aliases can be any unique string
37
+ # at the beginning of an underscored word. For example, to choose the first completion between 'so_long'
38
+ # and 'so_larger', type 's_lo'.
39
+ def underscore_search(input, list)
40
+ if input[/_([^_]+)$/]
41
+ regex = input.split('_').map {|e| Regexp.escape(e) }.join("([^_]+)?_")
42
+ list.select {|e| e =~ /^#{regex}/ }
43
+ else
44
+ normal_search(input, list)
45
+ end
46
+ end
47
+
48
+ # Default search across missions to be invoked by a search that wrap another search i.e. files_search.
49
+ def default_search(input, list)
50
+ send("#{Search.default_search}_search", input, list)
51
+ end
52
+
53
+ # Does default_search on the given paths but only returns ones that match the input's current
54
+ # directory depth, determined by '/'. For example if a user has typed 'irb/c', this search returns
55
+ # matching paths that are one directory deep i.e. 'irb/cmd/ irb/completion.rb irb/context.rb'.
56
+ def files_search(input, list)
57
+ incremental_filter(input, list, '/')
58
+ end
59
+
60
+ # Does the same as files_search but for modules. A module depth is delimited by '::'.
61
+ def modules_search(input, list)
62
+ incremental_filter(input, list, '::')
63
+ end
64
+
65
+ # Used by files_search and modules_search.
66
+ def incremental_filter(input, list, delim)
67
+ i = 0; input.gsub(delim) {|e| i+= 1 }
68
+ delim_chars = delim.split('').uniq.join('')
69
+ current_matches, future_matches = default_search(input, list).partition {|e|
70
+ e[/^[^#{delim_chars}]*(#{delim}[^#{delim_chars}]+){0,#{i}}$/] }
71
+ (current_matches + future_matches.map {|e| e[/^(([^#{delim_chars}]*#{delim}){0,#{i+1}})/, 1] }).uniq
72
+ end
73
+ end
74
+ end