rcodetools 0.4.0.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.
Files changed (66) hide show
  1. data/CHANGES +18 -0
  2. data/README +34 -0
  3. data/README.emacs +54 -0
  4. data/README.method_analysis +13 -0
  5. data/README.vim +84 -0
  6. data/README.xmpfilter +202 -0
  7. data/Rakefile +123 -0
  8. data/Rakefile.method_analysis +30 -0
  9. data/THANKS +6 -0
  10. data/bin/rct-complete +37 -0
  11. data/bin/rct-doc +50 -0
  12. data/bin/rct-meth-args +392 -0
  13. data/bin/xmpfilter +75 -0
  14. data/icicles-rcodetools.el +31 -0
  15. data/lib/method_analyzer.rb +107 -0
  16. data/lib/rcodetools/completion.rb +282 -0
  17. data/lib/rcodetools/doc.rb +176 -0
  18. data/lib/rcodetools/options.rb +83 -0
  19. data/lib/rcodetools/xmpfilter.rb +208 -0
  20. data/lib/rcodetools/xmptestunitfilter.rb +197 -0
  21. data/rcodetools.el +162 -0
  22. data/rcodetools.vim +118 -0
  23. data/setup.rb +1585 -0
  24. data/test/data/add_markers-input.rb +2 -0
  25. data/test/data/add_markers-output.rb +2 -0
  26. data/test/data/bindings-input.rb +26 -0
  27. data/test/data/bindings-output.rb +31 -0
  28. data/test/data/completion-input.rb +1 -0
  29. data/test/data/completion-output.rb +2 -0
  30. data/test/data/completion_emacs-input.rb +1 -0
  31. data/test/data/completion_emacs-output.rb +5 -0
  32. data/test/data/completion_emacs_icicles-input.rb +1 -0
  33. data/test/data/completion_emacs_icicles-output.rb +5 -0
  34. data/test/data/doc-input.rb +1 -0
  35. data/test/data/doc-output.rb +1 -0
  36. data/test/data/method_analyzer-data.rb +33 -0
  37. data/test/data/method_args.data.rb +106 -0
  38. data/test/data/no_warnings-input.rb +3 -0
  39. data/test/data/no_warnings-output.rb +4 -0
  40. data/test/data/refe-input.rb +1 -0
  41. data/test/data/refe-output.rb +1 -0
  42. data/test/data/ri-input.rb +1 -0
  43. data/test/data/ri-output.rb +1 -0
  44. data/test/data/ri_emacs-input.rb +1 -0
  45. data/test/data/ri_emacs-output.rb +1 -0
  46. data/test/data/ri_vim-input.rb +1 -0
  47. data/test/data/ri_vim-output.rb +1 -0
  48. data/test/data/rspec-input.rb +48 -0
  49. data/test/data/rspec-output.rb +52 -0
  50. data/test/data/rspec_poetry-input.rb +48 -0
  51. data/test/data/rspec_poetry-output.rb +52 -0
  52. data/test/data/simple_annotation-input.rb +8 -0
  53. data/test/data/simple_annotation-output.rb +8 -0
  54. data/test/data/unit_test-input.rb +50 -0
  55. data/test/data/unit_test-output.rb +52 -0
  56. data/test/data/unit_test_poetry-input.rb +50 -0
  57. data/test/data/unit_test_poetry-output.rb +52 -0
  58. data/test/test_completion.rb +467 -0
  59. data/test/test_doc.rb +403 -0
  60. data/test/test_functional.rb +18 -0
  61. data/test/test_method_analyzer.rb +99 -0
  62. data/test/test_method_args.rb +134 -0
  63. data/test/test_run.rb +41 -0
  64. data/test/test_xmpfilter.rb +36 -0
  65. data/test/test_xmptestunitfilter.rb +84 -0
  66. metadata +139 -0
data/bin/xmpfilter ADDED
@@ -0,0 +1,75 @@
1
+ #! /home/batsman/usr//bin/ruby
2
+ require 'rcodetools/xmpfilter'
3
+ require 'rcodetools/options'
4
+
5
+ options = DEFAULT_OPTIONS
6
+ rails_settings = false
7
+ klass = XMPFilter
8
+
9
+ opts = OptionParser.new do |opts|
10
+ opts.extend OptionHandler
11
+ opts.set_banner
12
+
13
+ opts.separator ""
14
+ opts.separator "Modes:"
15
+ opts.on("-a", "--annotations", "Annotate code (default)") do
16
+ klass = XMPFilter
17
+ end
18
+ opts.on("-u", "--unittest", "Complete Test::Unit assertions.") do
19
+ require 'rcodetools/xmptestunitfilter'
20
+ klass = XMPTestUnitFilter
21
+ end
22
+ opts.on("-s", "--spec", "Complete RSpec expectations.") do
23
+ require 'rcodetools/xmptestunitfilter'
24
+ klass = XMPRSpecFilter
25
+ options[:interpreter] = "spec"
26
+ end
27
+ opts.on("-m", "--markers", "Add # => markers.") do
28
+ klass = XMPAddMarkers
29
+ end
30
+
31
+ opts.handle_interpreter options
32
+
33
+ opts.separator ""
34
+ opts.separator "Specific options:"
35
+ opts.on("-l N", "--min-line-length N", Integer, "Align markers to N spaces.") do |min_codeline_size|
36
+ options[:min_codeline_size] = min_codeline_size
37
+ end
38
+ opts.on("--rails", "Setting appropriate for Rails.",
39
+ "(no warnings, find working directory,",
40
+ " Test::Unit assertions)") do
41
+ require 'rcodetools/xmptestunitfilter'
42
+ options[:warnings] = false
43
+ klass = XMPTestUnitFilter
44
+ rails_settings = true
45
+ end
46
+ opts.on("--[no-]poetry", "Whether to use extra parentheses.",
47
+ "(default: use them)") do |poetry_p|
48
+ options[:use_parentheses] = !poetry_p
49
+ end
50
+ opts.on("--[no-]warnings", "Whether to add warnings (# !>).",
51
+ "(default: enabled)") {|warnings_p| options[:warnings] = warnings_p }
52
+ opts.on("-q", "--quiet", "Supress standard output.") do
53
+ options[:output_stdout] = false
54
+ end
55
+
56
+ opts.handle_misc options
57
+ end
58
+
59
+ set_extra_opts options
60
+ opts.parse!(ARGV)
61
+
62
+ if rails_settings && !options[:wd]
63
+ if File.exist? ARGF.path
64
+ options[:wd] = File.dirname(ARGF.path)
65
+ elsif File.exist? "test/unit"
66
+ options[:wd] = "test/unit"
67
+ elsif File.exist? "unit"
68
+ options[:wd] = "unit"
69
+ end
70
+ end
71
+ targetcode = ARGF.read
72
+ Dir.chdir options[:wd] if options[:wd]
73
+
74
+ # Do the job. dispatched by klass.
75
+ puts klass.run(targetcode, options)
@@ -0,0 +1,31 @@
1
+ ;;; icicles-rcodetools.el -- accurate completion with icicles
2
+
3
+ ;;; Copyright (c) 2006 rubikitch <rubikitch@ruby-lang.org>
4
+ ;;;
5
+ ;;; Use and distribution subject to the terms of the Ruby license.
6
+
7
+ (require 'icicles)
8
+ (require 'rcodetools)
9
+
10
+ (setq rct-complete-symbol-function 'rct-complete-symbol--icicles)
11
+ (icicle-define-command rct-complete-symbol--icicles
12
+ "Perform ruby method and class completion on the text around point with icicles.
13
+ C-M-RET shows RI documentation on each candidate.
14
+ See also `rct-interactive'."
15
+
16
+ (lambda (result)
17
+ (save-excursion
18
+ (search-backward pattern)
19
+ (setq beg (point)))
20
+ (delete-region beg end)
21
+ (insert result)) ;/function
22
+ "rct-complete: " ;prompt
23
+ rct-method-completion-table
24
+ nil t pattern nil nil nil
25
+ ((end (point)) beg
26
+ pattern klass
27
+ (icicle-candidate-help-fn
28
+ (lambda (result) (ri result klass)))) ;bindings
29
+ (rct-exec-and-eval rct-complete-command-name "--completion-emacs-icicles"))
30
+
31
+ (provide 'icicles-rcodetools)
@@ -0,0 +1,107 @@
1
+ class Module
2
+ remove_method :attr_reader
3
+ def attr_reader(*names)
4
+ names.each do |name|
5
+ module_eval "def #{name}() @#{name} end"
6
+ end
7
+ end
8
+ remove_method :attr_writer
9
+ def attr_writer(*names)
10
+ names.each do |name|
11
+ module_eval "def #{name}=(x) @#{name}=x end"
12
+ end
13
+ end
14
+ remove_method :attr_accessor
15
+ def attr_accessor(*names)
16
+ attr_reader(*names)
17
+ attr_writer(*names)
18
+ end
19
+ remove_method :attr
20
+ def attr(name, writer=false)
21
+ attr_reader name
22
+ attr_writer name if writer
23
+ end
24
+ end
25
+
26
+
27
+ module MethodAnalyzer
28
+ @@methods = Hash.new{ |h,k| h[k] = Hash.new{ |h,k| h[k] = []} }
29
+ @@whereis = []
30
+ @@expand_path = Hash.new{ |h,k| h[k] = File.expand_path(k)}
31
+
32
+ def self.trace_func(event, file, line, id, binding, klass, *rest)
33
+ return if file == __FILE__
34
+ return if (event != 'call' and event != 'c-call')
35
+ return if klass == Class and id == :inherited
36
+ return if klass == Module and id == :method_added
37
+ return if klass == Kernel and id == :singleton_method_added
38
+ saved_crit = Thread.critical
39
+ Thread.critical = true
40
+
41
+ the_self = eval("self",binding)
42
+ flag = Class === the_self ? "." : "#"
43
+ #klass = klass == Kernel ? Object : klass
44
+ fullname = "#{klass}#{flag}#{id}"
45
+ file.replace @@expand_path[file]
46
+ if event == 'call'
47
+ @@whereis << [file, line, fullname] if file !~ /\(eval\)$/
48
+ file, line, rest = caller(4)[0].split(/:/)
49
+ file.replace @@expand_path[file] # DRY
50
+ p caller(0) if $DEBUG
51
+ line = line.to_i
52
+ end
53
+ @@methods[file][line] << fullname if event =~ /call/
54
+
55
+ Thread.critical = saved_crit
56
+ end
57
+
58
+ def self.at_exit__output_marshal
59
+ at_exit do
60
+ set_trace_func nil
61
+ dbfile = "method_analysis"
62
+ old = Marshal.load(File.read(dbfile)) rescue {}
63
+ open(dbfile, "wb") do |io|
64
+ # Because Marshal.dump cannot handle hashes with default_proc
65
+ @@methods.default = nil
66
+ @@methods.each_value{ |v| v.default=nil; v.each_value{ |vv| vv.uniq! } }
67
+ Marshal.dump(@@methods.merge(old), io)
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+ def self.at_exit__output_text
74
+ at_exit do
75
+ set_trace_func nil
76
+ puts "method fullnames"
77
+ @@methods.sort.each do |file, lines|
78
+ lines.sort.each do |line, methods|
79
+ printf "%s:%s:%s\n", file, line, methods.uniq.join(" ")
80
+ end
81
+ end
82
+
83
+ puts
84
+ puts "method definitions"
85
+ @@whereis.sort.uniq.each do |file, line, fullname |
86
+ printf "%s:%s:%s\n", file, line, fullname
87
+ end
88
+
89
+ end
90
+ end
91
+
92
+ def self.set_at_exit
93
+ case ENV['METHOD_ANALYZER_FORMAT']
94
+ when 'marshal'
95
+ at_exit__output_marshal
96
+ else
97
+ at_exit__output_text
98
+ end
99
+ end
100
+
101
+ set_at_exit
102
+ set_trace_func method(:trace_func).to_proc
103
+ end
104
+
105
+ if __FILE__ == $0
106
+ load "./test/data/method_analyzer-data.rb"
107
+ end
@@ -0,0 +1,282 @@
1
+ require 'rcodetools/xmpfilter'
2
+ require 'enumerator'
3
+ # Common routines for XMPCompletionFilter/XMPDocFilter
4
+ module ProcessParticularLine
5
+ def fill_literal!(expr)
6
+ [ "\"", "'", "`" ].each do |q|
7
+ expr.gsub!(/#{q}(.+)#{q}/){ '"' + "x"*$1.length + '"' }
8
+ end
9
+ expr.gsub!(/(%([wWqQxrs])?(\W))(.+?)\3/){
10
+ percent = $2 == 'x' ? '%'+$3 : $1 # avoid executing shell command
11
+ percent + "x"*$4.length + $3
12
+ }
13
+ [ %w[( )], %w[{ }], %w![ ]!, %w[< >] ].each do |b,e|
14
+ rb, re = [b,e].map{ |x| Regexp.quote(x)}
15
+ expr.gsub!(/(%([wWqQxrs])?(#{rb}))(.+)#{re}/){
16
+ percent = $2 == 'x' ? '%'+$3 : $1 # avoid executing shell command
17
+ percent + "x"*$4.length + e
18
+ }
19
+ end
20
+ end
21
+
22
+ module ExpressionExtension
23
+ attr_accessor :eval_string
24
+ attr_accessor :meth
25
+ end
26
+ OPERATOR_CHARS = '\|^&<>=~\+\-\*\/%\['
27
+ def set_expr_and_postfix!(expr, column, &regexp)
28
+ expr.extend ExpressionExtension
29
+
30
+ @postfix = ""
31
+ expr_orig = expr.clone
32
+ column ||= expr.length
33
+ last_char = expr[column-1]
34
+ expr.replace expr[ regexp[column] ]
35
+ debugprint "expr_orig=#{expr_orig}", "expr(sliced)=#{expr}"
36
+ right_stripped = Regexp.last_match.post_match
37
+ _handle_do_end right_stripped
38
+ aref_or_aset = aref_or_aset? right_stripped, last_char
39
+ debugprint "aref_or_aset=#{aref_or_aset.inspect}"
40
+ set_last_word! expr, aref_or_aset
41
+ fill_literal! expr_orig
42
+ _handle_brackets expr_orig, expr
43
+ expr << aref_or_aset if aref_or_aset
44
+ _handle_keywords expr_orig, column
45
+ debugprint "expr(processed)=#{expr}"
46
+ expr
47
+ end
48
+
49
+ def _handle_do_end(right_stripped)
50
+ right_stripped << "\n"
51
+ n_do = right_stripped.scan(/[\s\)]do\s/).length
52
+ n_end = right_stripped.scan(/\bend\b/).length
53
+ @postfix = ";begin" * (n_do - n_end)
54
+ end
55
+
56
+ def _handle_brackets(expr_orig, expr)
57
+ [ %w[{ }], %w[( )], %w![ ]! ].each do |left, right|
58
+ n_left = expr_orig.count(left) - expr.count(left)
59
+ n_right = expr_orig.count(right) - expr.count(right)
60
+ n = n_left - n_right
61
+ @postfix << ";#{left}" * n if n >= 0
62
+ end
63
+ end
64
+
65
+ def _handle_keywords(expr_orig, column)
66
+ %w[if unless while until for].each do |keyw|
67
+ pos = expr_orig.index(/\b#{keyw}\b/)
68
+ @postfix << ";begin" if pos and pos < column # if * xxx
69
+
70
+ pos = expr_orig.index(/;\s*#{keyw}\b/)
71
+ @postfix << ";begin" if pos and column < pos # * ; if xxx
72
+ end
73
+ end
74
+
75
+ def aref_or_aset?(right_stripped, last_char)
76
+ if last_char == ?[
77
+ case right_stripped
78
+ when /\]\s*=/: "[]="
79
+ when /\]/: "[]"
80
+ end
81
+ end
82
+ end
83
+
84
+ def set_last_word!(expr, aref_or_aset=nil)
85
+ debugprint "expr(before set_last_word)=#{expr}"
86
+ if aref_or_aset
87
+ opchars = ""
88
+ else
89
+ opchars = expr.slice!(/\s*[#{OPERATOR_CHARS}]+$/)
90
+ debugprint "expr(strip opchars)=#{expr}"
91
+ end
92
+
93
+ expr.replace(if expr =~ /[\"\'\`]$/ # String operations
94
+ "''"
95
+ else
96
+ fill_literal! expr
97
+ phrase = current_phrase(expr)
98
+ if aref_or_aset
99
+ expr.eval_string = expr[0..-2]
100
+ expr.meth = aref_or_aset
101
+ elsif phrase.match( /^(.+)\.(.*)$/ )
102
+ expr.eval_string, expr.meth = $1, $2
103
+ elsif opchars != ''
104
+ expr
105
+ end
106
+ debugprint "expr.eval_string=#{expr.eval_string}", "expr.meth=#{expr.meth}"
107
+ phrase
108
+ end << (opchars || '')) # ` font-lock hack
109
+ debugprint "expr(after set_last_word)=#{expr}"
110
+ end
111
+
112
+ def current_phrase(expr)
113
+ paren_level = 0
114
+ start = 0
115
+ (expr.length-1).downto(0) do |i|
116
+ c = expr[i,1]
117
+ if c =~ /[\)\}\]]/
118
+ paren_level += 1
119
+ next
120
+ end
121
+ if paren_level > 0
122
+ next if c =~ /[, ]/
123
+ else
124
+ break (start = i+1) if c =~ /[ ,\(\{\[]/
125
+ end
126
+ if c =~ /[\(\{\[]/
127
+ paren_level -= 1
128
+ break (start = i+1) if paren_level < 0
129
+ end
130
+ end
131
+ expr[start..-1]
132
+ end
133
+
134
+ def add_BEGIN
135
+ <<XXX
136
+ BEGIN {
137
+ class Object
138
+ def method_missing(meth, *args, &block)
139
+ # ignore NoMethodError
140
+ end
141
+ end
142
+ }
143
+ XXX
144
+ end
145
+
146
+ class RuntimeDataError < RuntimeError; end
147
+ class NewCodeError < Exception; end
148
+ def runtime_data_with_class(code, lineno, column=nil)
149
+ newcode = code.to_a.enum_with_index.map{|line, i|
150
+ i+1==lineno ? prepare_line(line.chomp, column) : line
151
+ }.join
152
+ newcode << add_BEGIN if @ignore_NoMethodError
153
+ debugprint "newcode", newcode, "-"*80
154
+ stdout, stderr = execute(newcode)
155
+ output = stderr.readlines
156
+ debugprint "stdout", output, "-"*80
157
+ output = output.reject{|x| /^-:[0-9]+: warning/.match(x)}
158
+ runtime_data = extract_data(output)
159
+ if exception = /^-:[0-9]+:.*/m.match(output.join)
160
+ raise NewCodeError, exception[0].chomp
161
+ end
162
+ begin
163
+ dat = runtime_data.results[1][0]
164
+ [dat[0], dat[1..-1].to_s]
165
+ rescue
166
+ raise RuntimeDataError, runtime_data.inspect
167
+ end
168
+ end
169
+
170
+ def runtime_data(code, lineno, column=nil)
171
+ runtime_data_with_class(code, lineno, column)[1]
172
+ end
173
+
174
+ end
175
+
176
+ # Nearly 100% accurate completion for any editors!!
177
+ # by rubikitch <rubikitch@ruby-lang.org>
178
+ class XMPCompletionFilter < XMPFilter
179
+ include ProcessParticularLine
180
+
181
+ # String completion begins with this.
182
+ attr :prefix
183
+
184
+ def self.run(code, opts)
185
+ new(opts).completion_code(code, opts[:lineno], opts[:column])
186
+ end
187
+
188
+ def prepare_line(expr, column)
189
+ set_expr_and_postfix!(expr, column){|c| /^.{#{c}}/ }
190
+ @prefix = expr
191
+ case expr
192
+ when /^\$\w+$/ # global variable
193
+ __prepare_line 'global_variables'
194
+ when /^@@\w+$/ # class variable
195
+ __prepare_line 'Module === self ? class_variables : self.class.class_variables'
196
+ when /^@\w+$/ # instance variable
197
+ __prepare_line 'instance_variables'
198
+ when /^([A-Z].*)::(.*)$/ # nested constants / class methods
199
+ @prefix = $2
200
+ __prepare_line "#$1.constants | #$1.methods(true)"
201
+ when /^[A-Z]\w*$/ # normal constants
202
+ __prepare_line 'Module.constants'
203
+ when /^::(.+)::(.*)$/ # toplevel nested constants
204
+ @prefix = $2
205
+ __prepare_line "::#$1.constants | ::#$1.methods"
206
+ when /^::(.*)/ # toplevel constant
207
+ @prefix = $1
208
+ __prepare_line 'Object.constants'
209
+ when /^(:[^:.]*)$/ # symbol
210
+ __prepare_line 'Symbol.all_symbols.map{|s| ":" + s.id2name}'
211
+ when /\.([^.]*)$/ # method call
212
+ @prefix = $1
213
+ __prepare_line "(#{Regexp.last_match.pre_match}).methods(true)"
214
+ else # bare words
215
+ __prepare_line "methods | private_methods | local_variables | self.class.constants"
216
+ end
217
+ end
218
+
219
+ def __prepare_line(all_completion_expr)
220
+ v = "#{VAR}"
221
+ idx = 1
222
+ oneline_ize(<<EOC)
223
+ #{v} = (#{all_completion_expr}).grep(/^#{Regexp.quote(@prefix)}/)
224
+ $stderr.puts("#{MARKER}[#{idx}] => " + #{v}.class.to_s + " " + #{v}.join(" ")) || #{v}
225
+ exit
226
+ EOC
227
+ end
228
+
229
+ # Array of completion candidates.
230
+ def candidates(code, lineno, column=nil)
231
+ methods = runtime_data(code, lineno, column) rescue ""
232
+ methods.split.sort
233
+ end
234
+
235
+ # Completion code for editors.
236
+ def completion_code(code, lineno, column=nil)
237
+ candidates(code, lineno, column).join("\n")
238
+ end
239
+ end
240
+
241
+ class XMPCompletionEmacsFilter < XMPCompletionFilter
242
+ def completion_code(code, lineno, column=nil)
243
+ elisp = "(progn\n"
244
+ elisp << "(setq rct-method-completion-table '("
245
+ begin
246
+ candidates(code, lineno, column).each do |meth|
247
+ elisp << format('("%s") ', meth)
248
+ end
249
+ rescue Exception => err
250
+ return %Q[(error "#{err.message}")]
251
+ end
252
+ elisp << "))\n"
253
+ elisp << %Q[(setq pattern "#{prefix}")\n]
254
+ elisp << %Q[(try-completion pattern rct-method-completion-table nil)\n]
255
+ elisp << ")" # /progn
256
+ end
257
+ end
258
+
259
+ class XMPCompletionEmacsIciclesFilter < XMPCompletionFilter
260
+ def candidates(code, lineno, column=nil)
261
+ klass, methods = runtime_data_with_class(code, lineno, column) rescue ["", ""]
262
+ @klass = klass
263
+ methods.split.sort
264
+ end
265
+
266
+ def completion_code(code, lineno, column=nil)
267
+ elisp = "(progn\n"
268
+ elisp << "(setq rct-method-completion-table '("
269
+ begin
270
+ candidates(code, lineno, column).each do |meth|
271
+ elisp << format('("%s") ', meth)
272
+ end
273
+ rescue Exception => err
274
+ return %Q[(error "#{err.message}")]
275
+ end
276
+ elisp << "))\n"
277
+ elisp << %Q[(setq pattern "#{prefix}")\n]
278
+ elisp << %Q[(setq klass "#{@klass}")\n]
279
+ elisp << ")" # /progn
280
+ end
281
+ end
282
+