I have several files that I have to read, check the contents, and then
write any changes. I overloaded the File class to help me out with
this:
----------------------------------------------------------
class File
def self.change(filename)
# read the file, execute a block, then write the file
File.open(filename, 'r+') do |file|
lines = file.readlines
yield lines
# return if not changing anything
file.pos = 0
file.print lines
file.truncate(file.pos)
end
end
end
----------------------------------------------------------
The method is called with:
----------------------------------------------------------
File.change('awesomefile') do |contents|
contents.collect! do |line|
# magic happens
# decide not to change anything
end
end
----------------------------------------------------------
This is a huge waste if the files don't change (as they often don't).
The problem I'm having is with the "# decide not to change anything" in
the block and passing that back to the method so "# return if not
changing anything" knows the answer. I'm not sure if I can pass an
instance variable around to do this? I don't really know how that would
work since I don't really have an instance of file to work with.
Maybe someone has some ideas?
on 19.08.2008 07:25
on 19.08.2008 08:17
On Tue, Aug 19, 2008 at 7:21 AM, James Dinkel <jdinkel@gmail.com> wrote: > I have several files that I have to read, check the contents, and then > write any changes. I overloaded the File class to help me out with > this: > This is a huge waste if the files don't change (as they often don't). > The problem I'm having is with the "# decide not to change anything" in > the block and passing that back to the method so "# return if not > changing anything" knows the answer. I'm not sure if I can pass an > instance variable around to do this? I don't really know how that would > work since I don't really have an instance of file to work with. > > Maybe someone has some ideas? Untested, but what about: ---------------------------------------------------------- class File def self.change(filename) # read the file, execute a block, then write the file File.open(filename, 'r+') do |file| lines = file.readlines new_lines = yield lines # return if not changing anything return unless new_lines file.pos = 0 file.print new_lines file.truncate(file.pos) end end end ---------------------------------------------------------- The method is called with: ---------------------------------------------------------- File.change('awesomefile') do |contents| contents.collect! do |line| # magic happens # decide not to change anything --> the last statement evaluates to nil nil # when you want to return the new lines end end ---------------------------------------------------------- Of course you should change the logic in the block to not be within a collect! when you want to return nil. Hope this helps, Jesus.
on 19.08.2008 08:40
James Dinkel wrote: > File.open(filename, 'r+') do |file| > > > This is a huge waste if the files don't change (as they often don't). > The problem I'm having is with the "# decide not to change anything" in > the block and passing that back to the method so "# return if not > changing anything" knows the answer. I'm not sure if I can pass an > instance variable around to do this? I don't really know how that would > work since I don't really have an instance of file to work with. > > Maybe someone has some ideas? This works well. However, you have to be careful the last statement in your block doesn't accidentally evaluate to nil, so I added a true at the end just in case. class File def self.change(filename) # read the file, execute a block, then write the file File.open(filename, 'r+') do|file| lines = file.readlines # return if not changing anything return if yield(lines) == false file.pos = 0 file.print lines file.truncate(file.pos) end end end File.change('test.txt') do|contents| break false if contents.size < 5 contents.each do|c| c.gsub!(/[[:digit:]]/, 'X') end true end You can also use catch and throw. I think this method is a little cleaner. class File def self.change(filename) catch(:nochanges) do # read the file, execute a block, then write the file File.open(filename, 'r+') do|file| lines = file.readlines # return if not changing anything return if yield(lines) == false file.pos = 0 file.print lines file.truncate(file.pos) end end end end File.change('test.txt') do|contents| throw :nochanges if contents.size < 5 contents.each do|c| c.gsub!(/[[:digit:]]/, 'X') end end
on 19.08.2008 15:40
Michael Morin wrote: > > You can also use catch and throw. I think this method is a little > cleaner. > ah, I think I like the catch and throw method. That's pretty close to what I was visualizing in my head. Just one thing though: > class File > > def self.change(filename) > catch(:nochanges) do > # read the file, execute a block, then write the file > File.open(filename, 'r+') do|file| > lines = file.readlines > > # return if not changing anything > return if yield(lines) == false > > file.pos = 0 > file.print lines > file.truncate(file.pos) > end > end > end > end > You left in the "return if yield(lines) == false", was that a typo? I'm guessing it was.
on 20.08.2008 01:54
James Dinkel wrote: >> class File >> file.pos = 0 >> file.print lines >> file.truncate(file.pos) >> end >> end >> end >> end >> > > You left in the "return if yield(lines) == false", was that a typo? I'm > guessing it was. Oh yeah, that is a typo left over from the other example. That line should read just "yield lines".
on 20.08.2008 17:04
Michael Morin wrote: > > Oh yeah, that is a typo left over from the other example. That line > should read just "yield lines". That's what I figured. Thanks a bunch. My method ended up like this: ---------------------------------------------------------- class File def self.change(filename) # read the file, execute a block, then write the file File.open(filename, 'r+') do |file| lines = file.readlines catch :nochanges yield lines file.pos = 0 file.print lines file.truncate(file.pos) end end end end ----------------------------------------------------------