将 Ruby 源文件中的函数替换为 Ruby
Replacing a function in a Ruby source file with Ruby
首先,我使用的是 Ruby 2.2.0 和 Rails 4.2.0(所以我安装了 ActiveSupport,如果其中有任何对这项任务有帮助的话)。
什么
我想要做的是读取一个 Ruby 文件并采用其中声明的任何方法,然后将它们写入/覆盖到另一个可能已经有这些的 Ruby 文件中声明的方法。
我想在源文件中做这个,它是作为一种预处理器的事情来做的,而不是在 运行 的时候。我不是在谈论 Open Classes,我想永久编辑源文件。
如果什么都没有,那么我会做一些读取文件的事情,逐行查找 def
和 end
语句,但我想知道是否已经存在这样的东西?
为什么
部分是为了工作,部分是为了看看我是否可以,我在 Ruby 中编写了一个脚本,它解析由 ArgoXML 生成的 XMI 文件,在我的绘图中使用一些进一步的约束,以便我的脚本知道我什么时候我正在使用 has_many
或 has_many :through
,后者我用于我所有的 habtm。
然后它在 shell 脚本中为我的所有模型等创建 Rails 生成器命令,并使用 awk 注入任何需要手动设置的关系(如不符合发电机)。
这为大约 100 个表快速创建了模型和数据库条目。显然,虽然 运行再次使用我的脚本会删除任何现有文件(或者问我 100 次我想做什么,考虑到大部分时间都被销毁,我宁愿不这样做)。
示例:
我很高兴只有一个带有函数声明的 class,或者我可以将它放在一个命名的 class 或模块中,这都没有关系。文件中永远只有一个 class。
我想要发生的是读取文件 A 的内容,从文件中获取每个方法。然后我想将这些方法覆盖到文件 B 的源代码中,以便我得到文件 C。
文件A(方法需要插入文件B的文件)
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def some_other_method
puts "FooBar"
end
文件 B(部分 - 标准 Rails 脚手架,覆盖前)
class RulesController < ApplicationController
before_action :set_rule, only: [:show, :edit, :update, :destroy]
# GET /rules
# GET /rules.json
def index
@rules = Rule.all
end
# GET /rules/1
# GET /rules/1.json
def show
end
# GET /rules/new
def new
@rule = Rule.new
end
# GET /rules/1/edit
def edit
end
. . . etc
end
文件C(部分-我想要的输出文件)
class RulesController < ApplicationController
before_action :set_rule, only: [:show, :edit, :update, :destroy]
# GET /rules
# GET /rules.json
def index
@rules = Rule.all
end
# GET /rules/1
# GET /rules/1.json
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def some_other_method
puts "FooBar"
end
# GET /rules/new
def new
@rule = Rule.new
end
# GET /rules/1/edit
def edit
end
end
更新
注意:我意识到这是错误的 atm 并且正在修复它。
由于我的 XMI 解析器 class 中已经有一个变量 @models,它以简洁的格式包含了我的 XMI 上的所有信息,我可以向我的解析器添加一个函数来破解模板。
我使用了@ChrisHeald 提供的代码,将它放在一个名为 hack_templates.rb 的文件中,并使其接受命令行 ARGV 作为实现和模板文件位置。
我的新函数然后在我的输出中添加额外的 shell 命令行。
def hack_templates
hacks = []
for idref, model in @models do
# Controllers
controller_name = "#{model['name'].tableize}_controller.rb"
controller_implementation = "./db/init/controllers/#{controller_name}"
countroller_template = "./app/controllers/#{controller_name}"
if File.exist?(countroller_template) and File.exist?(controller_implementation) then
puts "ruby hack_template.rb #{controller_implementation} #{countroller_template}"
end
# Models
# Views
end
hacks.join("\n")
end
@ChrisHeald 代码中的 hack_templates.rb:
require 'method_source'
include MethodSource::CodeHelpers
$implementation = open(ARGV[0])
$template = open(ARGV[1])
module Implementation
class << self
eval $implementation.read
end
end
@template_by_line = $template.each_line.to_a
(Implementation.methods - Module.new.methods).each do |method|
$implementation.rewind
# Get the source code of our implementation method
impl_source = expression_at $implementation, Implementation.method(method).source_location[1]
# Get the line that this method starts on in the template
template_line = @template_by_line.index {|line| line.match(/^\s*def #{method}/) }
if template_line
# If we found a match, replace it
# Get the source code for the template method
tmpl_source = expression_at @template_by_line, template_line + 1
# Replace it with the implemetation method
@template_by_line[template_line] = impl_source
# Remove any extra lines from the template method
tmpl_source.split(/\n/).length.times do |len|
@template_by_line.delete_at template_line + len + 1
end
else
# find the last `end` in the template array and insert the implementation source before it
last_end = @template_by_line.rindex {|line| line.match(/^\s*end/)}
@template_by_line.insert(last_end, "\n" + impl_source + "\n")
end
end
File.open($template, "w+") do |f|
f.syswrite(@template_by_line.join)
end
这只会从第二个 运行 开始起作用,因为控制器在我调用此代码的地方不存在,除非那里有旧控制器。对我来说,只 运行 两次可能很容易,但我也可以将 hack_template.rb 命令发送到与主 output.sh
不同的文件
进一步更新
最后我最终使用了 Rails 关注点。我按照 ClassNameConcern 的命名约定为每个 class 创建了一个新关注点。然后我将我所有的 class 方法放在一个问题中,让我的模型只剩下自动生成的东西。
因为我有大约 100 个模型,到目前为止我已经编辑了 4 个模型,所以效果很好。不过,@ChrisHeald 的回答仍然是我问题的正确答案。
发电机
最简单的方法就是使用自定义生成器模板。
In Rails 3.0 and above, generators don't just look in the source root for templates, they also search for templates in other paths. And one of them is lib/templates. Since we want to customize Rails::Generators::HelperGenerator, we can do that by simply making a template copy inside lib/templates/rails/helper with the name helper.rb.
控制器模板在这里:
所以您可以在 lib/templates/rails/controller/controller.rb
中创建一个副本,然后将其自定义为:
<% if namespaced? -%>
require_dependency "<%= namespaced_path %>/application_controller"
<% end -%>
<% module_namespacing do -%>
class <%= class_name %>Controller < ApplicationController
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def some_other_method
puts "FooBar"
end
<% (actions - [:show, :some_other_method]).each do |action| -%>
def <%= action %>
end
<%= "\n" unless action == actions.last -%>
<% end -%>
end
<% end -%>
然后,当您生成控制器时,Rails 将使用您的模板(与您的实现)。
就是说,如果这些方法在您生成的所有控制器中都是通用的,您应该考虑简单地从通用超类继承您的控制器,然后让 show
的默认实现类似于:
def show
super
end
还有 post 的选项 - 使用 source_location
处理生成的模板以检测方法边界,但这很繁琐。我目前正在为此进行概念验证。
实际上是在修改模板
我们将使用 method_source
gem,它使用 Method#source_location
来查找方法的起始位置,然后计算行直到找到完整的表达式。一旦我们有了它,那么我们就可以用我们的实现替换给定的方法。
这太糟糕了,如果可行,我建议不要这样做。
implementation.rb
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def some_other_method
puts "FooBar"
end
template.rb
class RulesController < ApplicationController
before_action :set_rule, only: [:show, :edit, :update, :destroy]
# GET /rules
# GET /rules.json
def index
@rules = Rule.all
end
# GET /rules/1
# GET /rules/1.json
def show
end
# GET /rules/new
def new
@rule = Rule.new
end
# GET /rules/1/edit
def edit
end
# . . . etc
end
converter.rb
require 'method_source'
include MethodSource::CodeHelpers
$implementation = open("implementation.rb")
$template = open("template.rb")
module Implementation
class << self
eval $implementation.read
end
end
@template_by_line = $template.each_line.to_a
(Implementation.methods - Module.new.methods).each do |method|
$implementation.rewind
# Get the source code of our implementation method
impl_source = expression_at $implementation, Implementation.method(method).source_location[1]
# Get the line that this method starts on in the template
template_line = @template_by_line.index {|line| line.match(/^\s*def #{method}/) }
if template_line
# If we found a match, replace it
# Get the source code for the template method
tmpl_source = expression_at @template_by_line, template_line + 1
# Replace it with the implemetation method
@template_by_line[template_line] = impl_source
# Remove any extra lines from the template method
tmpl_source.split(/\n/).length.times do |len|
@template_by_line.delete_at template_line + len + 1
end
else
# find the last `end` in the template array and insert the implementation source before it
last_end = @template_by_line.rindex {|line| line.match(/^\s*end/)}
@template_by_line.insert(last_end, "\n" + impl_source + "\n")
end
end
puts @template_by_line.join
还有一些输出:
$ ruby converter.rb
class RulesController < ApplicationController
before_action :set_rule, only: [:show, :edit, :update, :destroy]
# GET /rules
# GET /rules.json
def index
@rules = Rule.all
end
# GET /rules/1
# GET /rules/1.json
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def new
@rule = Rule.new
end
# GET /rules/1/edit
def edit
def some_other_method
puts "FooBar"
end end
# . . . etc
end
首先,我使用的是 Ruby 2.2.0 和 Rails 4.2.0(所以我安装了 ActiveSupport,如果其中有任何对这项任务有帮助的话)。
什么
我想要做的是读取一个 Ruby 文件并采用其中声明的任何方法,然后将它们写入/覆盖到另一个可能已经有这些的 Ruby 文件中声明的方法。
我想在源文件中做这个,它是作为一种预处理器的事情来做的,而不是在 运行 的时候。我不是在谈论 Open Classes,我想永久编辑源文件。
如果什么都没有,那么我会做一些读取文件的事情,逐行查找 def
和 end
语句,但我想知道是否已经存在这样的东西?
为什么
部分是为了工作,部分是为了看看我是否可以,我在 Ruby 中编写了一个脚本,它解析由 ArgoXML 生成的 XMI 文件,在我的绘图中使用一些进一步的约束,以便我的脚本知道我什么时候我正在使用 has_many
或 has_many :through
,后者我用于我所有的 habtm。
然后它在 shell 脚本中为我的所有模型等创建 Rails 生成器命令,并使用 awk 注入任何需要手动设置的关系(如不符合发电机)。
这为大约 100 个表快速创建了模型和数据库条目。显然,虽然 运行再次使用我的脚本会删除任何现有文件(或者问我 100 次我想做什么,考虑到大部分时间都被销毁,我宁愿不这样做)。
示例:
我很高兴只有一个带有函数声明的 class,或者我可以将它放在一个命名的 class 或模块中,这都没有关系。文件中永远只有一个 class。
我想要发生的是读取文件 A 的内容,从文件中获取每个方法。然后我想将这些方法覆盖到文件 B 的源代码中,以便我得到文件 C。
文件A(方法需要插入文件B的文件)
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def some_other_method
puts "FooBar"
end
文件 B(部分 - 标准 Rails 脚手架,覆盖前)
class RulesController < ApplicationController
before_action :set_rule, only: [:show, :edit, :update, :destroy]
# GET /rules
# GET /rules.json
def index
@rules = Rule.all
end
# GET /rules/1
# GET /rules/1.json
def show
end
# GET /rules/new
def new
@rule = Rule.new
end
# GET /rules/1/edit
def edit
end
. . . etc
end
文件C(部分-我想要的输出文件)
class RulesController < ApplicationController
before_action :set_rule, only: [:show, :edit, :update, :destroy]
# GET /rules
# GET /rules.json
def index
@rules = Rule.all
end
# GET /rules/1
# GET /rules/1.json
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def some_other_method
puts "FooBar"
end
# GET /rules/new
def new
@rule = Rule.new
end
# GET /rules/1/edit
def edit
end
end
更新 注意:我意识到这是错误的 atm 并且正在修复它。
由于我的 XMI 解析器 class 中已经有一个变量 @models,它以简洁的格式包含了我的 XMI 上的所有信息,我可以向我的解析器添加一个函数来破解模板。
我使用了@ChrisHeald 提供的代码,将它放在一个名为 hack_templates.rb 的文件中,并使其接受命令行 ARGV 作为实现和模板文件位置。
我的新函数然后在我的输出中添加额外的 shell 命令行。
def hack_templates
hacks = []
for idref, model in @models do
# Controllers
controller_name = "#{model['name'].tableize}_controller.rb"
controller_implementation = "./db/init/controllers/#{controller_name}"
countroller_template = "./app/controllers/#{controller_name}"
if File.exist?(countroller_template) and File.exist?(controller_implementation) then
puts "ruby hack_template.rb #{controller_implementation} #{countroller_template}"
end
# Models
# Views
end
hacks.join("\n")
end
@ChrisHeald 代码中的 hack_templates.rb:
require 'method_source'
include MethodSource::CodeHelpers
$implementation = open(ARGV[0])
$template = open(ARGV[1])
module Implementation
class << self
eval $implementation.read
end
end
@template_by_line = $template.each_line.to_a
(Implementation.methods - Module.new.methods).each do |method|
$implementation.rewind
# Get the source code of our implementation method
impl_source = expression_at $implementation, Implementation.method(method).source_location[1]
# Get the line that this method starts on in the template
template_line = @template_by_line.index {|line| line.match(/^\s*def #{method}/) }
if template_line
# If we found a match, replace it
# Get the source code for the template method
tmpl_source = expression_at @template_by_line, template_line + 1
# Replace it with the implemetation method
@template_by_line[template_line] = impl_source
# Remove any extra lines from the template method
tmpl_source.split(/\n/).length.times do |len|
@template_by_line.delete_at template_line + len + 1
end
else
# find the last `end` in the template array and insert the implementation source before it
last_end = @template_by_line.rindex {|line| line.match(/^\s*end/)}
@template_by_line.insert(last_end, "\n" + impl_source + "\n")
end
end
File.open($template, "w+") do |f|
f.syswrite(@template_by_line.join)
end
这只会从第二个 运行 开始起作用,因为控制器在我调用此代码的地方不存在,除非那里有旧控制器。对我来说,只 运行 两次可能很容易,但我也可以将 hack_template.rb 命令发送到与主 output.sh
不同的文件进一步更新 最后我最终使用了 Rails 关注点。我按照 ClassNameConcern 的命名约定为每个 class 创建了一个新关注点。然后我将我所有的 class 方法放在一个问题中,让我的模型只剩下自动生成的东西。
因为我有大约 100 个模型,到目前为止我已经编辑了 4 个模型,所以效果很好。不过,@ChrisHeald 的回答仍然是我问题的正确答案。
发电机
最简单的方法就是使用自定义生成器模板。
In Rails 3.0 and above, generators don't just look in the source root for templates, they also search for templates in other paths. And one of them is lib/templates. Since we want to customize Rails::Generators::HelperGenerator, we can do that by simply making a template copy inside lib/templates/rails/helper with the name helper.rb.
控制器模板在这里:
所以您可以在 lib/templates/rails/controller/controller.rb
中创建一个副本,然后将其自定义为:
<% if namespaced? -%>
require_dependency "<%= namespaced_path %>/application_controller"
<% end -%>
<% module_namespacing do -%>
class <%= class_name %>Controller < ApplicationController
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def some_other_method
puts "FooBar"
end
<% (actions - [:show, :some_other_method]).each do |action| -%>
def <%= action %>
end
<%= "\n" unless action == actions.last -%>
<% end -%>
end
<% end -%>
然后,当您生成控制器时,Rails 将使用您的模板(与您的实现)。
就是说,如果这些方法在您生成的所有控制器中都是通用的,您应该考虑简单地从通用超类继承您的控制器,然后让 show
的默认实现类似于:
def show
super
end
还有 post 的选项 - 使用 source_location
处理生成的模板以检测方法边界,但这很繁琐。我目前正在为此进行概念验证。
实际上是在修改模板
我们将使用 method_source
gem,它使用 Method#source_location
来查找方法的起始位置,然后计算行直到找到完整的表达式。一旦我们有了它,那么我们就可以用我们的实现替换给定的方法。
这太糟糕了,如果可行,我建议不要这样做。
implementation.rb
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def some_other_method
puts "FooBar"
end
template.rb
class RulesController < ApplicationController
before_action :set_rule, only: [:show, :edit, :update, :destroy]
# GET /rules
# GET /rules.json
def index
@rules = Rule.all
end
# GET /rules/1
# GET /rules/1.json
def show
end
# GET /rules/new
def new
@rule = Rule.new
end
# GET /rules/1/edit
def edit
end
# . . . etc
end
converter.rb
require 'method_source'
include MethodSource::CodeHelpers
$implementation = open("implementation.rb")
$template = open("template.rb")
module Implementation
class << self
eval $implementation.read
end
end
@template_by_line = $template.each_line.to_a
(Implementation.methods - Module.new.methods).each do |method|
$implementation.rewind
# Get the source code of our implementation method
impl_source = expression_at $implementation, Implementation.method(method).source_location[1]
# Get the line that this method starts on in the template
template_line = @template_by_line.index {|line| line.match(/^\s*def #{method}/) }
if template_line
# If we found a match, replace it
# Get the source code for the template method
tmpl_source = expression_at @template_by_line, template_line + 1
# Replace it with the implemetation method
@template_by_line[template_line] = impl_source
# Remove any extra lines from the template method
tmpl_source.split(/\n/).length.times do |len|
@template_by_line.delete_at template_line + len + 1
end
else
# find the last `end` in the template array and insert the implementation source before it
last_end = @template_by_line.rindex {|line| line.match(/^\s*end/)}
@template_by_line.insert(last_end, "\n" + impl_source + "\n")
end
end
puts @template_by_line.join
还有一些输出:
$ ruby converter.rb
class RulesController < ApplicationController
before_action :set_rule, only: [:show, :edit, :update, :destroy]
# GET /rules
# GET /rules.json
def index
@rules = Rule.all
end
# GET /rules/1
# GET /rules/1.json
def show
puts params[:rule_set_edition_id]
@description = @rule.description
@input_lines = @description.split("\n")
@output_lines = []
for input_line in @input_lines do
if input_line != "" then
@output_lines << input_line
end
end
@rule_set_edition
end
def new
@rule = Rule.new
end
# GET /rules/1/edit
def edit
def some_other_method
puts "FooBar"
end end
# . . . etc
end