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
@@ -0,0 +1,176 @@
1
+ require 'rcodetools/completion'
2
+ # Call Ri for any editors!!
3
+ # by rubikitch <rubikitch@ruby-lang.org>
4
+ class XMPDocFilter < XMPFilter
5
+ include ProcessParticularLine
6
+
7
+ def initialize(opts = {})
8
+ super
9
+ @filename = opts[:filename]
10
+ extend UseMethodAnalyzer if opts[:use_method_analyzer]
11
+ end
12
+
13
+ def self.run(code, opts)
14
+ new(opts).doc(code, opts[:lineno], opts[:column])
15
+ end
16
+
17
+ def prepare_line(expr, column)
18
+ set_expr_and_postfix!(expr, column){|c|
19
+ withop_re = /^.{#{c-1}}[#{OPERATOR_CHARS}]+/
20
+ if expr =~ withop_re
21
+ withop_re
22
+ else
23
+ /^.{#{c}}[\w#{OPERATOR_CHARS}]*/
24
+ end
25
+ }
26
+ recv = expr
27
+
28
+ # When expr already knows receiver and method,
29
+ return(__prepare_line :recv => expr.eval_string, :meth => expr.meth) if expr.eval_string
30
+
31
+ case expr
32
+ when /^(?:::)?([A-Z].*)(?:::|\.)(.*)$/ # nested constants / class methods
33
+ __prepare_line :klass => $1, :meth_or_constant => $2
34
+ when /^(?:::)?[A-Z]/ # normal constants
35
+ __prepare_line :klass => expr
36
+ when /\.([^.]*)$/ # method call
37
+ __prepare_line :recv => Regexp.last_match.pre_match, :meth => $1
38
+ when /^(.+)(\[\]=?)$/ # [], []=
39
+ __prepare_line :recv => $1, :meth => $2
40
+ when /[#{OPERATOR_CHARS}]+$/ # operator
41
+ __prepare_line :recv => Regexp.last_match.pre_match, :meth => $&
42
+ else # bare words
43
+ __prepare_line :recv => "self", :meth => expr
44
+ end
45
+ end
46
+
47
+ def __prepare_line(x)
48
+ v = "#{VAR}"
49
+ klass = "#{VAR}_klass"
50
+ flag = "#{VAR}_flag"
51
+ which_methods = "#{VAR}_methods"
52
+ ancestor_class = "#{VAR}_ancestor_class"
53
+ idx = 1
54
+ recv = x[:recv] || x[:klass] || raise(ArgumentError, "need :recv or :klass")
55
+ meth = x[:meth_or_constant] || x[:meth]
56
+ debugprint "recv=#{recv}", "meth=#{meth}"
57
+ if meth
58
+ code = <<-EOC
59
+ #{v} = (#{recv})
60
+ if Class === #{v}
61
+ #{flag} = #{v}.respond_to?('#{meth}') ? "." : "::"
62
+ #{klass} = #{v}
63
+ #{which_methods} = :methods
64
+ else
65
+ #{flag} = "#"
66
+ #{klass} = #{v}.class
67
+ #{which_methods} = :instance_methods
68
+ end
69
+ #{ancestor_class} = #{klass}.ancestors.delete_if{|c| c==Kernel }.find{|c| c.__send__(#{which_methods}, false).include? '#{meth}' }
70
+ $stderr.print("#{MARKER}[#{idx}] => " + #{v}.class.to_s + " ")
71
+
72
+ if #{ancestor_class}
73
+ $stderr.puts(#{ancestor_class}.to_s + #{flag} + '#{meth}')
74
+ else
75
+ [Kernel, Module, Class].each do |k|
76
+ if (k.instance_methods(false) + k.private_instance_methods(false)).include? '#{meth}'
77
+ $stderr.printf("%s#%s\\n", k, '#{meth}'); exit
78
+ end
79
+ end
80
+ $stderr.puts(#{v}.to_s + '::' + '#{meth}')
81
+ end
82
+ exit
83
+ EOC
84
+ else
85
+ code = <<-EOC
86
+ #{v} = (#{recv})
87
+ $stderr.print("#{MARKER}[#{idx}] => " + #{v}.class.to_s + " ")
88
+ $stderr.puts(#{v}.to_s)
89
+ exit
90
+ EOC
91
+ end
92
+ oneline_ize(code)
93
+ end
94
+
95
+ # overridable by module
96
+ def _doc(code, lineno, column)
97
+ end
98
+
99
+ def doc(code, lineno, column=nil)
100
+ _doc(code, lineno, column) or runtime_data(code, lineno, column).to_s
101
+ end
102
+
103
+ module UseMethodAnalyzer
104
+ METHOD_ANALYSIS = "method_analysis"
105
+ def have_method_analysis
106
+ File.file? METHOD_ANALYSIS
107
+ end
108
+
109
+ def find_method_analysis
110
+ here = Dir.pwd
111
+ oldpwd = here
112
+ begin
113
+ while ! have_method_analysis
114
+ Dir.chdir("..")
115
+ if Dir.pwd == here
116
+ return nil # not found
117
+ end
118
+ here = Dir.pwd
119
+ end
120
+ ensure
121
+ Dir.chdir oldpwd
122
+ end
123
+ yield(File.join(here, METHOD_ANALYSIS))
124
+ end
125
+
126
+ def _doc(code, lineno, column=nil)
127
+ find_method_analysis do |ma_file|
128
+ methods = open(ma_file, "rb"){ |f| Marshal.load(f)}
129
+ line = File.readlines(@filename)[lineno-1]
130
+ current_method = line[ /^.{#{column}}\w*/][ /\w+[\?!]?$/ ].sub(/:+/,'')
131
+ filename = @filename # FIXME
132
+ begin
133
+ methods[filename][lineno].grep(Regexp.new(Regexp.quote(current_method)))[0]
134
+ rescue NoMethodError
135
+ raise "doc/method_analyzer:cannot find #{current_method}"
136
+ end
137
+
138
+ end
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ # ReFe is so-called `Japanese Ri'.
145
+ class XMPReFeFilter < XMPDocFilter
146
+ def doc(code, lineno, column=nil)
147
+ "refe '#{super}'"
148
+ end
149
+ end
150
+
151
+ class XMPRiFilter < XMPDocFilter
152
+ def doc(code, lineno, column=nil)
153
+ "ri '#{super}'"
154
+ end
155
+ end
156
+
157
+ class XMPRiEmacsFilter < XMPDocFilter
158
+ def doc(code, lineno, column=nil)
159
+ begin
160
+ %!(rct-find-tag-or-ri "#{super}")!
161
+ rescue Exception => err
162
+ return %Q[(error "#{err.message}")]
163
+ end
164
+ end
165
+ end
166
+
167
+ class XMPRiVimFilter < XMPDocFilter
168
+ def doc(code, lineno, column=nil)
169
+ begin
170
+ %{call RCT_find_tag_or_ri("#{super}")}
171
+ rescue Exception => err
172
+ return %Q[echo #{err.message.inspect}]
173
+ end
174
+ end
175
+ end
176
+
@@ -0,0 +1,83 @@
1
+ require 'optparse'
2
+
3
+ # Domain specific OptionParser extensions
4
+ module OptionHandler
5
+ def set_banner
6
+ self.banner = "Usage: #{$0} [options] [inputfile] [-- cmdline args]"
7
+ end
8
+
9
+ def handle_position(options)
10
+ separator ""
11
+ separator "Position options:"
12
+ on("--line=LINE", "Current line number.") do |n|
13
+ options[:lineno] = n.to_i
14
+ end
15
+ on("--column=COLUMN", "Current column number.") do |n|
16
+ options[:column] = n.to_i
17
+ end
18
+ end
19
+
20
+ def handle_interpreter(options)
21
+ separator ""
22
+ separator "Interpreter options:"
23
+ on("-S FILE", "--interpreter FILE", "Use interpreter FILE.") do |interpreter|
24
+ options[:interpreter] = interpreter
25
+ end
26
+ on("-I PATH", "Add PATH to $LOAD_PATH") do |path|
27
+ options[:include_paths] << path
28
+ end
29
+ on("-r LIB", "Require LIB before execution.") do |lib|
30
+ options[:libs] << lib
31
+ end
32
+ on("-e EXPR", "--eval=EXPR", "--stub=EXPR", "Evaluate EXPR after execution.") do |expr|
33
+ options[:evals] << expr
34
+ end
35
+
36
+ end
37
+
38
+ def handle_misc(options)
39
+ separator ""
40
+ separator "Misc options:"
41
+ on("--cd DIR", "Change working directory to DIR.") do |dir|
42
+ options[:wd] = dir
43
+ end
44
+ on("--debug", "Write transformed source code to xmp-tmp.PID.rb.") do
45
+ options[:dump] = "xmp-tmp.#{Process.pid}.rb"
46
+ end
47
+ separator ""
48
+ on("-h", "--help", "Show this message") do
49
+ puts self
50
+ exit
51
+ end
52
+ on("-v", "--version", "Show version information") do
53
+ puts "#{File.basename($0)} #{XMPFilter::VERSION}"
54
+ exit
55
+ end
56
+ end
57
+
58
+
59
+ end
60
+
61
+ def set_extra_opts(options)
62
+ if idx = ARGV.index("--")
63
+ options[:options] = ARGV[idx+1..-1]
64
+ ARGV.replace ARGV[0...idx]
65
+ else
66
+ options[:options] = []
67
+ end
68
+ end
69
+
70
+ DEFAULT_OPTIONS = {
71
+ :interpreter => "ruby",
72
+ :options => ["hoge"],
73
+ :min_codeline_size => 50,
74
+ :libs => [],
75
+ :evals => [],
76
+ :include_paths => [],
77
+ :dump => nil,
78
+ :wd => nil,
79
+ :warnings => true,
80
+ :use_parentheses => true,
81
+ :column => nil,
82
+ :output_stdout => true,
83
+ }
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (c) 2005-2006 Mauricio Fernandez <mfp@acm.org> http://eigenclass.org
3
+ # rubikitch <rubikitch@ruby-lang.org>
4
+ # Use and distribution subject to the terms of the Ruby license.
5
+
6
+ class XMPFilter
7
+ VERSION = "0.4.0"
8
+
9
+ MARKER = "!XMP#{Time.new.to_i}_#{Process.pid}_#{rand(1000000)}!"
10
+ XMP_RE = Regexp.new("^" + Regexp.escape(MARKER) + '\[([0-9]+)\] (=>|~>|==>) (.*)')
11
+ VAR = "_xmp_#{Time.new.to_i}_#{Process.pid}_#{rand(1000000)}"
12
+ WARNING_RE = /.*:([0-9]+): warning: (.*)/
13
+
14
+ RuntimeData = Struct.new(:results, :exceptions, :bindings)
15
+
16
+ INITIALIZE_OPTS = {:interpreter => "ruby", :options => [], :libs => [],
17
+ :include_paths => [], :warnings => true,
18
+ :use_parentheses => true}
19
+
20
+ # The processor (overridable)
21
+ def self.run(code, opts)
22
+ new(opts).annotate(code)
23
+ end
24
+
25
+ def initialize(opts = {})
26
+ options = INITIALIZE_OPTS.merge opts
27
+ @interpreter = options[:interpreter]
28
+ @options = options[:options]
29
+ @libs = options[:libs]
30
+ @evals = options[:evals] || []
31
+ @include_paths = options[:include_paths]
32
+ @output_stdout = options[:output_stdout]
33
+ @dump = options[:dump]
34
+ @warnings = options[:warnings]
35
+ @parentheses = options[:use_parentheses]
36
+ @ignore_NoMethodError = options[:ignore_NoMethodError]
37
+
38
+ @postfix = ""
39
+ end
40
+
41
+ def add_markers(code, min_codeline_size = 50)
42
+ maxlen = code.map{|x| x.size}.max
43
+ maxlen = [min_codeline_size, maxlen + 2].max
44
+ ret = ""
45
+ code.each do |l|
46
+ l = l.chomp.gsub(/ # (=>|!>).*/, "").gsub(/\s*$/, "")
47
+ ret << (l + " " * (maxlen - l.size) + " # =>\n")
48
+ end
49
+ ret
50
+ end
51
+
52
+ def annotate(code)
53
+ idx = 0
54
+ newcode = code.gsub(/^(.*) # =>.*/){|l| prepare_line($1, idx += 1) }
55
+ if @dump
56
+ File.open(@dump, "w"){|f| f.puts newcode}
57
+ end
58
+ stdout, stderr = execute(newcode)
59
+ output = stderr.readlines
60
+ runtime_data = extract_data(output)
61
+ idx = 0
62
+ annotated = code.gsub(/^(.*) # =>.*/) do |l|
63
+ expr = $1
64
+ if /^\s*#/ =~ l
65
+ l
66
+ else
67
+ annotated_line(l, expr, runtime_data, idx += 1)
68
+ end
69
+ end.gsub(/ # !>.*/, '').gsub(/# (>>|~>)[^\n]*\n/m, "");
70
+ ret = final_decoration(annotated, output)
71
+ if @output_stdout and (s = stdout.read) != ""
72
+ ret << s.inject(""){|s,line| s + "# >> #{line}".chomp + "\n" }
73
+ end
74
+ ret
75
+ end
76
+
77
+ def annotated_line(line, expression, runtime_data, idx)
78
+ "#{expression} # => " + (runtime_data.results[idx].map{|x| x[1]} || []).join(", ")
79
+ end
80
+
81
+ def prepare_line_annotation(expr, idx)
82
+ v = "#{VAR}"
83
+ blocal = "__#{VAR}"
84
+ blocal2 = "___#{VAR}"
85
+ # rubikitch: oneline-ized
86
+ # <<EOF.chomp
87
+ # ((#{v} = (#{expr}); $stderr.puts("#{MARKER}[#{idx}] => " + #{v}.class.to_s + " " + #{v}.inspect) || begin; $stderr.puts local_variables; local_variables.each{|#{blocal}| #{blocal2} = eval(#{blocal}); if #{v} == #{blocal2} && #{blocal} != %#{expr}.strip; $stderr.puts("#{MARKER}[#{idx}] ==> " + #{blocal}); elsif [#{blocal2}] == #{v}; $stderr.puts("#{MARKER}[#{idx}] ==> [" + #{blocal} + "]") end }; nil rescue Exception; nil end || #{v}))
88
+ # EOF
89
+ oneline_ize(<<-EOF).chomp
90
+ #{v} = (#{expr})
91
+ $stderr.puts("#{MARKER}[#{idx}] => " + #{v}.class.to_s + " " + #{v}.inspect) || begin
92
+ $stderr.puts local_variables
93
+ local_variables.each{|#{blocal}|
94
+ #{blocal2} = eval(#{blocal})
95
+ if #{v} == #{blocal2} && #{blocal} != %#{expr}.strip
96
+ $stderr.puts("#{MARKER}[#{idx}] ==> " + #{blocal})
97
+ elsif [#{blocal2}] == #{v}
98
+ $stderr.puts("#{MARKER}[#{idx}] ==> [" + #{blocal} + "]")
99
+ end
100
+ }
101
+ nil
102
+ rescue Exception
103
+ nil
104
+ end || #{v}
105
+ EOF
106
+
107
+ end
108
+ alias_method :prepare_line, :prepare_line_annotation
109
+
110
+ def execute_tmpfile(code)
111
+ stdin, stdout, stderr = (1..3).map do |i|
112
+ fname = "xmpfilter.tmpfile_#{Process.pid}-#{i}.rb"
113
+ at_exit { File.unlink fname }
114
+ File.open(fname, "w+")
115
+ end
116
+ stdin.puts code
117
+ stdin.close
118
+ exe_line = <<-EOF.map{|l| l.strip}.join(";")
119
+ $stdout.reopen('#{stdout.path}', 'w')
120
+ $stderr.reopen('#{stderr.path}', 'w')
121
+ $0.replace '#{stdin.path}'
122
+ ARGV.replace(#{@options.inspect})
123
+ load #{stdin.path.inspect}
124
+ #{@evals.join(";")}
125
+ EOF
126
+ system(*(interpreter_command << "-e" << exe_line))
127
+ [stdout, stderr]
128
+ end
129
+
130
+ def execute_popen(code)
131
+ require 'open3'
132
+ stdin, stdout, stderr = Open3::popen3(*interpreter_command)
133
+ stdin.puts code
134
+ @evals.each{|x| stdin.puts x } unless @evals.empty?
135
+ stdin.close
136
+ [stdout, stderr]
137
+ end
138
+
139
+ if /win|mingw/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM
140
+ alias_method :execute, :execute_tmpfile
141
+ else
142
+ alias_method :execute, :execute_popen
143
+ end
144
+
145
+ def interpreter_command
146
+ r = [@interpreter, "-w"]
147
+ r << "-d" if $DEBUG
148
+ r << "-I#{@include_paths.join(":")}" unless @include_paths.empty?
149
+ @libs.each{|x| r << "-r#{x}" } unless @libs.empty?
150
+ (r << "-").concat @options unless @options.empty?
151
+ r
152
+ end
153
+
154
+ def extract_data(output)
155
+ results = Hash.new{|h,k| h[k] = []}
156
+ exceptions = Hash.new{|h,k| h[k] = []}
157
+ bindings = Hash.new{|h,k| h[k] = []}
158
+ output.grep(XMP_RE).each do |line|
159
+ result_id, op, result = XMP_RE.match(line).captures
160
+ case op
161
+ when "=>"
162
+ klass, value = /(\S+)\s+(.*)/.match(result).captures
163
+ results[result_id.to_i] << [klass, value]
164
+ when "~>"
165
+ exceptions[result_id.to_i] << result
166
+ when "==>"
167
+ bindings[result_id.to_i] << result unless result.index(VAR)
168
+ end
169
+ end
170
+ RuntimeData.new(results, exceptions, bindings)
171
+ end
172
+
173
+ def final_decoration(code, output)
174
+ warnings = {}
175
+ output.join.grep(WARNING_RE).map do |x|
176
+ md = WARNING_RE.match(x)
177
+ warnings[md[1].to_i] = md[2]
178
+ end
179
+ idx = 0
180
+ ret = code.map do |line|
181
+ w = warnings[idx+=1]
182
+ if @warnings
183
+ w ? (line.chomp + " # !> #{w}") : line
184
+ else
185
+ line
186
+ end
187
+ end
188
+ output = output.reject{|x| /^-:[0-9]+: warning/.match(x)}
189
+ if exception = /^-:[0-9]+:.*/m.match(output.join)
190
+ ret << exception[0].map{|line| "# ~> " + line }
191
+ end
192
+ ret
193
+ end
194
+
195
+ def oneline_ize(code)
196
+ "((" + code.gsub(/\r?\n|\r/, ';') + "))#{@postfix}\n"
197
+ end
198
+
199
+ def debugprint(*args)
200
+ $stderr.puts(*args) if $DEBUG
201
+ end
202
+ end # clas XMPFilter
203
+
204
+ class XMPAddMarkers < XMPFilter
205
+ def self.run(code, opts)
206
+ new(opts).add_markers(code, opts[:min_codeline_size])
207
+ end
208
+ end