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
@@ -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