在文件结尾之前插入文本

时间:2016-02-07 19:03:22

标签: ruby regex replace

我正在尝试编写一个脚本,该脚本将在Ruby文件中的最后一个end标记之前插入文本。例如,我想插入以下内容:

def hello
  puts "hello!"
end

在以下文件中,就在课程结束之前:

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  helper_method :authenticated?, :current_user

  def current_user?
    session[:current_user]   
  end

end

结果应如下所示:

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  helper_method :authenticated?, :current_user

  def current_user?
    session[:current_user]   
  end

  def hello
    puts "hello!"
  end

end

我试图找到一个与end的最后一次出现匹配的正则表达式,并将其替换为我要添加的块,但我尝试过的所有正则表达式仅匹配第一个end。试过这些:

  • end(?=[^end]*$)
  • end(?!.*end)
  • (.*)(end)(.*)

要替换字符串,我会执行以下操作(也许EOL字符会搞砸匹配?):

file_to_override = File.read("app/controllers/application_controller.rb")
file_to_override = file_to_override.sub(/end(?=[^end]*$)/, "#{new_string}\nend")

编辑:我也尝试使用How to replace the last occurrence of a substring in ruby?中提供的解决方案,但奇怪的是,它取代了 end的所有出现。

我做错了什么?谢谢!

4 个答案:

答案 0 :(得分:2)

这篇文章中解释的方法也在这里工作。您只需重新组织捕获组并使用/m修饰符强制.匹配换行符号。

new_string = <<EOS
  def hello
    puts "Hello!"
  end
EOS

file_to_override = <<EOS
class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  helper_method :authenticated?, :current_user

  def current_user?
    session[:current_user]   
  end

end
EOS

file_to_override=file_to_override.gsub(/(.*)(\nend\b.*)/m, "\\1\n#{new_string}\\2")
puts file_to_override

请参阅IDEONE demo

/(.*)(\nend\b.*)/m模式将匹配并捕获到第1组所有文本直到最后一个整个单词(由于\n之前和\b之后)end之前使用换行符,并将换行符“结束”以及剩余的任何内容放入第2组。在替换中,我们使用后向引用 \1和{{对已捕获的子字符串进行反向引用1}}并插入我们需要插入的字符串。

如果在上一个\2之后没有其他字词,您甚至可以使用end正则表达式。

答案 1 :(得分:1)

假设您将文件读入字符串text

text = <<_
class A
  def a
    'hi'
  end

end
_

并希望插入字符串to_enter

to_enter = <<_
  def hello
    puts "hello!"
  end
_

在最后end之前。你可以写

r = /
    .*             # match any number of any character (greedily)
    \K             # discard everything matched so far
    (?=\n\s*end\b) # match end-of-line, indenting spaces, and "end" followed
                   # by a word break in a positive lookahead
    /mx            # multi-line and extended/free-spacing regex definition modes

puts text.sub(r, to_enter)
(prints)
class A
  def a
    'hi'
  end
  def hello
    puts "hello!"
  end

end

请注意,sub正在用to_enter替换空字符串。

答案 2 :(得分:0)

编辑:来自Wiktor的回答正是我所寻求的。留下以下因为它也有效。

最后,我放弃了使用正则表达式替换。相反,我使用最后end的位置:

positions = file_to_override.enum_for(:scan, /end/).map { Regexp.last_match.begin(0) }

然后,在写入文件之前,我在字符串的最后位置添加了我需要的内容 - 1:

new_string = <<EOS
  def hello
    puts "Hello!"
  end
EOS

file_to_override[positions.last - 1] = "\n#{test_string}\n"
File.open("app/controllers/application_controller.rb", 'w') {|file| file.write(file_to_override)}

这有效,但对我来说它看起来不像惯用的Ruby。

答案 3 :(得分:0)

您还可以找到并替换“end”的最后一次出现(请注意,这也会与# Hello my friend中的结尾相匹配,但请参见下文)

# Our basics: In this text ...
original_content = "# myfile.rb\n"\
                   "module MyApp\n"\
                   "  class MyFile\n"\
                   "    def myfunc\n"\
                   "    end\n"\
                   "  end\n"\
                   "end\n"
# ...we want to inject this:
substitute = "# this will come to a final end!\n"\
             "end\n"

# Now find the last end ...
idx = original_content.rindex("end") # => index of last "end"(69)
# ... and substitute it
original_content[idx..idx+3] = substitute # (3 = "end".length)

这个解决方案有点老了(处理字符串中的索引在几年前感觉更冷)并且在这种形式下更“脆弱”,但避免你坐下来消化正则表达式。不要误会我的意思,正则表达式是一种令人难以置信的力量的工具,学习它们的时间是值得的。

也就是说,您可以使用rindex(例如rindex(/ *end/))来使用其他答案中的所有正则表达式。