繁体   English   中英

在给定ruby文件的情况下获取Ruby方法的开始和结束的行号

[英]Get line number of beginning and end of Ruby method given a ruby file

给定一个ruby文件,如何找到Ruby方法的开始和结束行?

例如说:

1 class Home
2   def initialize(color)
3     @color = color
4   end
5 end

给定文件home.rb和方法名initialize我希望接收(2,4)这是开始和结束行。

寻找end是棘手的。 我能想到的最好方法是使用解析器gem 基本上,您将把Ruby代码解析为AST,然后递归遍历其节点,直到找到类型为:def的第一个子节点为:initialize的节点:

require "parser/current"

def recursive_find(node, &block)
  return node if block.call(node)
  return nil unless node.respond_to?(:children) && !node.children.empty?
  node.children.each do |child_node|
    found = recursive_find(child_node, &block)
    return found if found
  end
  nil
end

src = <<END
  class Home
    def initialize(color)
      @color = color
    end
  end
END
ast = Parser::CurrentRuby.parse(src)

found = recursive_find(ast) do |node|
  node.respond_to?(:type) && node.type == :def && node.children[0] == :initialize
end

puts "Start: #{found.loc.first_line}"
puts "End: #{found.loc.last_line}"

# => Start: 2
#    End: 4

PS我本来会推荐标准库中的Ripper模块,但是据我所知,还没有办法从中获得最终结果。

Ruby有一个source_location方法,它为您提供文件和起始行:

class Home
  def initialize(color)
    @color = color
  end
end

p Home.new(1).method(:initialize).source_location
# => ["test2.rb", 2]

要找到终点,也许要寻找下一个def或EOF。

Ruby源代码不过是一个文本文件。 您可以使用linux命令查找方法行号

grep -nrw 'def initialize' home.rb | grep -oE '[0-9]+'

我假设该文件最多包含一个initialize方法的定义(尽管将这种方法推广到其他方法并不困难),并且该方法的定义不包含语法错误。 对于任何一种提取正确行范围的方法,可能都需要使用后一种假设。

唯一棘手的部分是找到包含end的行,该行是initialize方法的定义的最后一行。 我用过Kernel#eval来定位那条线。 自然地,每当要执行该方法时都必须谨慎,尽管在这里eval只是试图编译(而不执行)方法。

def get_start_end_offsets(fname)
  start = nil
  str = ''
  File.foreach(fname).with_index do |line, i|
    if start.nil?
      next unless line.lstrip.start_with?('def initialize')
      start = i
      str << line.lstrip.insert(4,'_')
    else
      str << line
      if line.strip == "end"
        begin
          rv = eval(str)
        rescue SyntaxError
          nil
        end
        return [start, i] unless rv.nil? 
      end
    end
  end
  nil
end

假设我们正在搜索如下创建的文件1

str = <<-_
class C
  def self.feline
    "cat"
  end
  def initialize(arr)
    @row_sums = arr.map do |row|
      row.reduce do |t,x|
        t+x
      end
    end
  end
  def speak(sound)
    puts sound
  end
end
_

FName = 'temp'
File.write(FName, str)
  #=> 203

我们首先搜索(去掉前导空格之后) "def initialize"开始的行。 那就是索引4处的行。 完成该方法定义的end一个索引是10 因此,我们希望该方法返回[4, 10]

让我们看看这是否就是我们得到的。

p get_start_end_offsets(FName)
  #=> [4, 10]

说明

变量start等于开始def initialize的行的索引(在删除前导空白之后)。 start最初为nil ,一直保持为nil直到找到"def initialize"行。 然后将start设置为该行的索引。

现在,我们寻找一条line例如line.strip #=> "end" 这可能是也可能不是方法终止的end 要确定它是否是我们eval包含从一个开始的所有行字符串def initialize到线等于end才发现。 如果eval引发SyntaxError异常,则end不会终止该方法。 该异常被挽救,并且返回nil 如果那end终止了该方法,则eval将返回:_initialize (这是事实)。 在那种情况下,该方法返回[start, i] ,其中i是该行的索引。 如果在文件中未找到initialize方法,则返回nil

我已经将"initialize"转换为"_initialize"以禁止显示警告(eval):1: warning: redefining Object#initialize may cause infinite loop

查看此SO问题的两个答案,以了解为什么要挽救SyntaxError

比较缩进

如果知道"def initialize..."总是缩进与终止方法定义的"end"行相同的缩进量(并且两者之间没有其他"end"行缩进相同),我们可以使用事实,以获取开始和结束行。 有很多方法可以做到这一点。 我将使用Ruby有点晦涩的触发器运算符。 这种方法将容忍语法错误。

def get_start_end_offsets(fname)
  indent = -1
  lines = File.foreach(fname).with_index.select do |line, i|
    cond1 = line.lstrip.start_with?('def initialize')
    indent = line.size - line.lstrip.size if cond1
    cond2 = line.strip == "end" && line.size - line.lstrip.size == indent
    cond1 .. cond2 ? true : false
  end
  return nil if lines.nil?
  lines.map(&:last).minmax
end

get_start_end_offsets(FName)
  #=> [4, 10] 

1该文件不必只包含代码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM