module ProcSource

Based heavily on code from github.com/imedo/background Big thanks to the imedo dev team!

Public Class Methods

find(filename, start_line=1, block_only=true) click to toggle source
# File lib/proc_source.rb, line 69
  def self.find(filename, start_line=1, block_only=true)
    lines, lexer = nil, nil
    retried = 0
    loop do
      lines = get_lines(filename, start_line)
      return nil if lines.nil?
      #p [start_line, lines[0]]
      if !line_has_open?(lines.join) && start_line >= 0
        start_line -= 1 and retried +=1 and redo 
      end
      lexer = RubyLex.new
      lexer.set_input(StringIO.new(lines.join))
      break
    end
    stoken, etoken, nesting = nil, nil, 0
    while token = lexer.token
      n = token.name
      
      if RubyToken::TkIDENTIFIER === token
        #nothing
      elsif token.open_tag? || RubyToken::TkfLBRACE === token
        nesting += 1
        stoken = token if nesting == 1
      elsif RubyToken::TkEND === token || RubyToken::TkRBRACE === token
        if nesting == 1
          etoken = token 
          break
        end
        nesting -= 1
      elsif RubyToken::TkLBRACE === token
        nesting += 1
      elsif RubyToken::TkBITOR === token && stoken
        #nothing
      elsif RubyToken::TkNL === token && stoken && etoken
        break if nesting <= 0
      else
        #p token
      end
    end
#     puts lines if etoken.nil?
    lines = lines[stoken.line_no-1 .. etoken.line_no-1]
    
    # Remove the crud before the block definition. 
    if block_only
      spaces = lines.last.match(/^\s+/)[0] rescue ''
      lines[0] = spaces << lines[0][stoken.char_no .. -1]
    end
    ps = ProcString.new lines.join
    ps.file, ps.lines = filename, start_line .. start_line+etoken.line_no-1
    
    ps
  end
get_lines(filename, start_line = 1) click to toggle source
# File lib/proc_source.rb, line 162
def self.get_lines(filename, start_line = 1)
  case filename
    when nil
      nil
    when "(irb)"         # special "(irb)" descriptor?
      IRB.conf[:MAIN_CONTEXT].io.line(start_line .. -2)
    when /^\(eval.+\)$/  # special "(eval...)" descriptor?
      EVAL_LINES__[filename][start_line .. -2]
    else                 # regular file
      # Ruby already parsed this file? (see disclaimer above)
      if defined?(SCRIPT_LINES__) && SCRIPT_LINES__[filename]
        SCRIPT_LINES__[filename][(start_line - 1) .. -1]
      # If the file exists we're going to try reading it in
      elsif File.exist?(filename)
        begin
          File.readlines(filename)[(start_line - 1) .. -1]
        rescue
          nil
        end
      end
  end
end
line_has_open?(str) click to toggle source

A hack for Ruby 1.9, otherwise returns true.

Ruby 1.9 returns an incorrect line number when a block is specified with do/end. It happens b/c the line number returned by Ruby 1.9 is based on the first line in the block which contains a token (i.e. not a new line or comment etc…).

NOTE: This won't work in cases where the incorrect line also contains a “do”.

# File lib/proc_source.rb, line 134
def self.line_has_open?(str)
  return true unless RUBY_VERSION >= '1.9'
  lexer = RubyLex.new
  lexer.set_input(StringIO.new(str))
  success = false
  while token = lexer.token
    case token
    when RubyToken::TkNL
      break
    when RubyToken::TkDO
      success = true
    when RubyToken::TkfLBRACE
      success = true
    when RubyToken::TkCONSTANT
      if token.name == "Proc" &&
         lexer.token.is_a?(RubyToken::TkDOT)
        method = lexer.token
        if method.is_a?(RubyToken::TkIDENTIFIER) &&
           method.name == "new"
          success = true
        end
      end
    end
  end
  success
end