[英]Replacing a function in a Ruby source file with Ruby
首先,我將Ruby 2.2.0與Rails 4.2.0結合使用(因此,如果其中有任何幫助,我將安裝ActiveSupport)。
什么
我想要做的是讀取一個Ruby文件並采用其中聲明的任何方法,然后將它們寫入/覆蓋到另一個可能已經聲明了這些方法的Ruby文件中。
我想在源文件中執行此操作,它應作為一種預處理程序完成,而不是在運行時完成。 我不是在談論開放類 ,而是要永久編輯源文件。
如果什么也沒有,那么我將做一些事情來讀取文件,逐行查找def
和end
語句,但是我想知道是否已經存在這樣的東西?
為什么
部分地與我一起工作,部分地看是否可以,我在Ruby中編寫了一個腳本,該腳本使用繪圖中的一些進一步的約束來解析由ArgoXML生成的XMI文件,以便我的腳本知道何時使用has_many
或has_many :through
,后來我用了所有的精力。
然后,它將在我的所有模型等的外殼腳本中創建Rails生成器命令,並使用awk注入需要手動設置的任何關系(因為與生成器不符)。
這很快為大約100個表創建了模型和數據庫條目。 顯然,盡管再次運行我的腳本會刪除所有現有文件(或問我要執行的操作100倍,考慮到大部分時間都在銷毀,我寧願不這樣做)。
例:
我很高興有一個帶有函數聲明的類,或者可以將其放在命名的類或模塊中,這沒關系。 文件中將永遠只有一個類。
我要發生的是讀取文件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 Parser類中已經有了一個@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 Concerns。 我遵循ClassNameConcern的命名約定為每個類創建新的關注點。 然后,我把所有的類方法都放在了一個問題上,使我的模型只剩下自動生成的東西。
由於我喜歡100個模型,到目前為止我已經編輯了4個模型,因此效果很好。 @ChrisHeald的答案仍然是我的問題的正確答案。
最簡單的方法是使用自定義生成器模板。
在Rails 3.0及更高版本中,生成器不僅在源根目錄中查找模板,而且還在其他路徑中搜索模板。 其中之一是lib / templates。 由於我們要自定義Rails :: Generators :: HelperGenerator,因此我們可以通過在lib / templates / rails / helper中簡單創建一個名為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
還有一些選項可以使用source_location
對生成的模板進行后處理,以檢測方法邊界,但這很麻煩。 目前,我正在為此進行概念驗證。
我們將使用method_source
gem,它使用Method#source_location
查找方法的起始位置,然后對行進行求值,直到找到完整的表達式。 一旦有了,就可以用實現替換給定的方法。
這是非常糟糕的,如果可行,我建議不要這樣做。
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
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
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.