[英]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.