class Rcodetools::XMPFilter

Constants

INITIALIZE_OPTS
INTERPRETER_FORK
INTERPRETER_RBTEST
INTERPRETER_RUBY
Interpreter
MARKER
MULTI_LINE_RE
RuntimeData
SINGLE_LINE_RE
VAR
VERSION
WARNING_RE
XMP_RE

Public Class Methods

detect_rbtest(code, opts) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 44
def self.detect_rbtest(code, opts)
  opts[:use_rbtest] ||= (opts[:detect_rbtest] and code =~ /^=begin test./) ? true : false
end
new(opts = {}) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 53
def initialize(opts = {})
  options = INITIALIZE_OPTS.merge opts
  @interpreter_info = INTERPRETER_RUBY
  @interpreter = options[:interpreter]
  @options = options[:options]
  @libs = options[:libs]
  @evals = options[:evals] || []
  @include_paths = options[:include_paths]
  @output_stdout = options[:output_stdout]
  @dump = options[:dump]
  @warnings = options[:warnings]
  @parentheses = options[:use_parentheses]
  @ignore_NoMethodError = options[:ignore_NoMethodError]
  test_script = options[:test_script]
  test_method = options[:test_method]
  filename = options[:filename]
  @execute_ruby_tmpfile = options[:execute_ruby_tmpfile]
  @postfix = ""
  @stdin_path = nil
  @width = options[:width]
  
  initialize_rct_fork if options[:detect_rct_fork]
  initialize_rbtest if options[:use_rbtest]
  initialize_for_test_script test_script, test_method, filename if test_script and !options[:use_rbtest]
end
run(code, opts) click to toggle source

The processor (overridable)

# File lib/rcodetools/xmpfilter.rb, line 49
def self.run(code, opts)
  new(opts).annotate(code)
end

Public Instance Methods

add_markers(code, min_codeline_size = 50) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 117
def add_markers(code, min_codeline_size = 50)
  maxlen = code.map{|x| x.size}.max
  maxlen = [min_codeline_size, maxlen + 2].max
  ret = ""
  code.each do |l|
    l = l.chomp.gsub(/ # (=>|!>).*/, "").gsub(/\s*$/, "")
    ret << (l + " " * (maxlen - l.size) + " # =>\n")
  end
  ret
end
annotate(code) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 130
def annotate(code)
  idx = 0
  code = code.gsub(/ # !>.*/, '')
  newcode = code.gsub(SINGLE_LINE_RE){ prepare_line($1, idx += 1) }
  newcode.gsub!(MULTI_LINE_RE){ prepare_line($1, idx += 1, true)}
  File.open(@dump, "w"){|f| f.puts newcode} if @dump
  stdout, stderr = execute(newcode)
  output = stderr.readlines
  runtime_data = extract_data(output)
  idx = 0
  annotated = code.gsub(SINGLE_LINE_RE) { |l|
    expr = $1
    if /^\s*#/ =~ l
      l 
    else
      annotated_line(l, expr, runtime_data, idx += 1)
    end
  }
  annotated.gsub!(/ # !>.*/, '')
  annotated.gsub!(/# (>>|~>)[^\n]*\n/, "");
  annotated.gsub!(MULTI_LINE_RE) { |l|
    annotated_multi_line(l, $1, $3, runtime_data, idx += 1)
  }
  ret = final_decoration(annotated, output)
  if @output_stdout and (s = stdout.read) != ""
    ret << s.inject(""){|s,line| s + "# >> #{line}".chomp + "\n" }
  end
  ret
end
annotated_line(line, expression, runtime_data, idx) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 160
def annotated_line(line, expression, runtime_data, idx)
  "#{expression} # => " + (runtime_data.results[idx].map{|x| x[1]} || []).join(", ")
end
annotated_multi_line(line, expression, indent, runtime_data, idx) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 164
def annotated_multi_line(line, expression, indent, runtime_data, idx)
  pretty = (runtime_data.results[idx].map{|x| x[1]} || []).join(", ")
  first, *rest = pretty.to_a
  rest.inject("#{expression}\n#{indent}# => #{first || "\n"}") {|s, l| s << "#{indent}#    " << l }
end
common_path(a, b) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 113
def common_path(a, b)
  (a.split(File::Separator) & b.split(File::Separator)).join(File::Separator)
end
debugprint(*args) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 338
def debugprint(*args)
  $stderr.puts(*args) if $DEBUG
end
execute(code) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 278
def execute(code)
  __send__ @interpreter_info.execute_method, code
end
execute_popen(code) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 247
def execute_popen(code)
  require 'open3'
  stdin, stdout, stderr = Open3::popen3(*interpreter_command)
  stdin.puts code
  @evals.each{|x| stdin.puts x } unless @evals.empty?
  stdin.close
  [stdout, stderr]
end
execute_ruby(code) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 210
def execute_ruby(code)
  meth = (windows? or @execute_ruby_tmpfile) ? :execute_tmpfile : :execute_popen
  __send__ meth, code
end
execute_script(code) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 256
def execute_script(code)
  path = File.expand_path("xmpfilter.tmpfile_#{Process.pid}.rb", Dir.tmpdir)
  File.open(path, "w"){|f| f.puts code}
  at_exit { File.unlink path if File.exist? path}
  stdout_path, stderr_path = (1..2).map do |i|
    fname = "xmpfilter.tmpfile_#{Process.pid}-#{i}.rb"
    File.expand_path(fname, Dir.tmpdir)
  end
  args = *(interpreter_command << %Q["#{path}"] << "2>" << 
    %Q["#{stderr_path}"] << ">" << %Q["#{stdout_path}"])
  system(args.join(" "))
  
  [stdout_path, stderr_path].map do |fullname|
    f = File.open(fullname, "r")
    at_exit {
      f.close unless f.closed?
      File.unlink fullname if File.exist? fullname
    }
    f
  end
end
execute_tmpfile(code) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 215
  def execute_tmpfile(code)
    ios = %w[_ stdin stdout stderr]
    stdin, stdout, stderr = (1..3).map do |i|
      fname = if $DEBUG
                "xmpfilter.tmpfile_#{ios[i]}.rb"
              else
                "xmpfilter.tmpfile_#{Process.pid}-#{i}.rb"
              end
      f = File.open(fname, "w+")
      at_exit { f.close unless f.closed?; File.unlink fname unless $DEBUG}
      f
    end
    stdin.puts code
    stdin.close
    @stdin_path = File.expand_path stdin.path
    exe_line = "      $stdout.reopen('#{File.expand_path(stdout.path)}', 'w')
      $stderr.reopen('#{File.expand_path(stderr.path)}', 'w')
      $0 = '#{File.expand_path(stdin.path)}'
      ARGV.replace(#{@options.inspect})
      load #{File.expand_path(stdin.path).inspect}
      #{@evals.join(";")}
".map{|l| l.strip}.join(";")
    debugprint "execute command = #{(interpreter_command << "-e" << exe_line).join ' '}"

    oldpwd = Dir.pwd
    @interpreter_info.chdir_proc and @interpreter_info.chdir_proc.call
    system(*(interpreter_command << "-e" << exe_line))
    Dir.chdir oldpwd
    [stdout, stderr]
  end
extract_data(output) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 291
def extract_data(output)
  results = Hash.new{|h,k| h[k] = []}
  exceptions = Hash.new{|h,k| h[k] = []}
  bindings = Hash.new{|h,k| h[k] = []}
  output.grep(XMP_RE).each do |line|
    result_id, op, result = XMP_RE.match(line).captures
    case op
    when "=>"
      klass, value = /(\S+)\s+(.*)/.match(result).captures
      results[result_id.to_i] << [klass, value.gsub(/PPPROTECT/, "\n")]
    when "~>"
      exceptions[result_id.to_i] << result
    when "==>"
      bindings[result_id.to_i] << result unless result.index(VAR) 
    end
  end
  RuntimeData.new(results, exceptions, bindings)
end
final_decoration(code, output) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 310
def final_decoration(code, output)
  warnings = {}
  output.join.grep(WARNING_RE).map do |x|
    md = WARNING_RE.match(x)
    warnings[md[1].to_i] = md[2]
  end
  idx = 0
  ret = code.map do |line|
    w = warnings[idx+=1]
    if @warnings
      w ? (line.chomp + " # !> #{w}") : line
    else
      line
    end
  end
  output = output.reject{|x| /^-:[0-9]+: warning/.match(x)}
  if exception = /^-e?:[0-9]+:.*|^(?!!XMP)[^\n]+:[0-9]+:in .*/.match(output.join)
    err = exception[0]
    err.gsub!(Regexp.union(@stdin_path), '-') if @stdin_path
    ret << err.map{|line| "# ~> " + line }
  end
  ret
end
get_test_method_from_lineno(filename, lineno) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 103
def get_test_method_from_lineno(filename, lineno)
  lines = File.readlines(filename)
  (lineno-1).downto(0) do |i|
    if lines[i] =~ /^ *def *(test_[A-Za-z0-9?!_]+)$/
      return $1
    end
  end
  nil
end
initialize_for_test_script(test_script, test_method, filename) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 89
def initialize_for_test_script(test_script, test_method, filename)
  test_script.replace File.expand_path(test_script)
  filename.replace File.expand_path(filename)
  unless test_script == filename
    basedir = common_path(test_script, filename)
    relative_filename = filename[basedir.length+1 .. -1].sub(%r^lib/!, '')
    @evals << %Q$LOADED_FEATURES << #{relative_filename.dump}!
    @evals << safe_require_code('test/unit')
    @evals << %Qload #{test_script.dump}!
  end
  test_method = get_test_method_from_lineno(test_script, test_method.to_i) if test_method =~ /^\d/
  @evals << %QTest::Unit::AutoRunner.run(false, nil, ["-n", #{test_method.dump}])! if test_method
end
initialize_rbtest() click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 85
def initialize_rbtest
  @interpreter_info = INTERPRETER_RBTEST
end
initialize_rct_fork() click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 79
def initialize_rct_fork
  if Fork::run?
    @interpreter_info = INTERPRETER_FORK
  end
end
interpreter_command() click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 282
def interpreter_command
  r = [ @interpreter ] + @interpreter_info.options
  r << "-d" if $DEBUG and @interpreter_info.accept_debug
  r << "-I#{@include_paths.join(":")}" if @interpreter_info.accept_include_paths and !@include_paths.empty?
  @libs.each{|x| r << "-r#{x}" } unless @libs.empty?
  (r << "-").concat @options unless @options.empty?
  r
end
oneline_ize(code) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 334
def oneline_ize(code)
  "((" + code.gsub(/\r?\n|\r/, ';') + "));#{@postfix}\n"
end
prepare_line(expr, idx, multi_line=false)
prepare_line_annotation(expr, idx, multi_line=false) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 170
  def prepare_line_annotation(expr, idx, multi_line=false)
    v = "#{VAR}"
    blocal = "__#{VAR}"
    blocal2 = "___#{VAR}"
    lastmatch = "____#{VAR}"
    if multi_line
      pp = safe_require_code "pp"
      result = "((begin; #{lastmatch} = $~; PP.pp(#{v}, '', #{@width-5}).gsub(/\\r?\\n/, 'PPPROTECT'); ensure; $~ = #{lastmatch} end))"
    else
      pp = ''
      result = "#{v}.inspect"
    end
    oneline_ize("#{pp}
#{v} = (#{expr})
$stderr.puts("#{MARKER}[#{idx}] => " + #{v}.class.to_s + " " + #{result}) || 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}
").chomp

  end
Also aliased as: prepare_line
windows?() click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 31
def windows?
  /win|mingw/ =~ RUBY_PLATFORM && /darwin/ !~ RUBY_PLATFORM
end

Private Instance Methods

safe_require_code(lib) click to toggle source
# File lib/rcodetools/xmpfilter.rb, line 204
def safe_require_code(lib)
  oldverbose = "$#{VAR}_old_verbose"
  "#{oldverbose} = $VERBOSE; $VERBOSE = false; require '#{lib}'; $VERBOSE = #{oldverbose}"
end