rcodetools 0.4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+