How do you make a generator that alters a file.
Im trying to make it so that it finds a pattern in a file and adds come con开发者_开发知识库tent to the line below it.
Rails' scaffold generator does this when it adds a route to config/routes.rb
It does this by calling a very simple method:
def gsub_file(relative_destination, regexp, *args, &block)
path = destination_path(relative_destination)
content = File.read(path).gsub(regexp, *args, &block)
File.open(path, 'wb') { |file| file.write(content) }
end
What it's doing is taking a path/file as the first argument, followed by a regexp pattern, gsub arguments, and the block. This is a protected method that you'll have to recreate in order to use. I'm not sure if destination_path
is something you'll have access to, so you'll probably want to pass in the exact path and skip any conversion.
To use gsub_file
, let's say you want to add tags to your user model. Here's how you would do it:
line = "class User < ActiveRecord::Base"
gsub_file 'app/models/user.rb', /(#{Regexp.escape(line)})/mi do |match|
"#{match}\n has_many :tags\n"
end
You're finding the specific line in the file, the class opener, and adding your has_many
line right underneath.
Beware though, because this is the most brittle way to add content, which is why routing is one of the only places that uses it. The example above would normally be handled with a mix-in.
I like Jaime's answer. But, when I started to utilize it, I realized I needed to make some modifications. Here is the sample code I am using:
private
def destination_path(path)
File.join(destination_root, path)
end
def sub_file(relative_file, search_text, replace_text)
path = destination_path(relative_file)
file_content = File.read(path)
unless file_content.include? replace_text
content = file_content.sub(/(#{Regexp.escape(search_text)})/mi, replace_text)
File.open(path, 'wb') { |file| file.write(content) }
end
end
First, gsub
will replace ALL instances of the search text; I only need one. Ther0fore I used sub
instead.
Next, I needed to check if the replacement string was already in place. Otherwise, I'd be repeating the insert if my rails generator was ran multiple times. So I wrapped the code in an unless
block.
Finally, I added the def destination_path()
for you.
Now, how would you use this in a rails generator? Here is an example of how I make sure simplecov is installed for rspec and cucumber:
def configure_simplecov
code = "#Simple Coverage\nrequire 'simplecov'\nSimpleCov.start"
sub_file 'spec/spec_helper.rb', search = "ENV[\"RAILS_ENV\"] ||= 'test'", "#{search}\n\n#{code}\n"
sub_file 'features/support/env.rb', search = "require 'cucumber/rails'", "#{search}\n\n#{code}\n"
end
There is probably a more elegant and DRY-er way to do this. I really liked how you can add a block of text the Jamie's example. Hopefully my example adds a bit more functionality and error-checking.
精彩评论