Created
March 18, 2010 20:25
-
-
Save JoshCheek/336838 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# got inspired to play with files based on this post http://www.ruby-forum.com/topic/206208 | |
# define the constants our MailRecord uses | |
class MailRecord | |
LINE_WIDTH = 9 # all lines have this size | |
LINES_PER_RECORD = 5 # title , id , to , from , blankline | |
RECORD_WIDTH = LINE_WIDTH * LINES_PER_RECORD | |
end | |
# our MailRecord is composed of lots of fixed-width records. | |
# we define how to deal with an individual record here | |
class MailRecord | |
class Record | |
attr_accessor :title , :id , :from , :to , :offset | |
# initialize a new MailRecord::Record | |
def initialize(file) | |
self.file = file # store the file for writing | |
self.offset = file.pos # store the offset so we know where to write | |
self.title = file.readline.strip | |
self.id = file.readline.split('=').last.to_i | |
self.from = file.readline.split('=').last.strip | |
self.to = file.readline.split('=').last.strip | |
end | |
# write the record back to the file | |
def write | |
file.seek offset , IO::SEEK_SET | |
file.puts self.to_s | |
end | |
# the record as a string (will be the same as it would be in the file, this allows us to directly write it) | |
# sprintf allows us to use a format string, and make it look the way we want | |
# the "% ... s" tells it that we will be putting a string into there | |
# the dash to the right of the percent tells it to left justify the results | |
# by interpolating LINE_WIDTH, we tell it how many characters in width we want it to be | |
# we subtract 1 from the LINE_WIDTH because we need to add a newline to the end | |
# so if LINE_WIDTH is 9, then that becomes %-8s\n" meaning that we want to put a left justified | |
# string with 8 characters into there. We multiply it by 5, to get "%-8s\n%-8s\n%-8s\n%-8s\n%-8s\n" | |
# which will have slots for 5 lines. | |
# | |
# Then we pass in the five strings we want to use: the title, id, from, to, and blankline strings. | |
# But we need to be careful not to exceed the maximum width and corrupt our file, so we validate each | |
# one to make sure it is within the 8 chars | |
def to_s | |
sprintf "%-#{LINE_WIDTH-1}s\n"*5 , | |
validate(title) , | |
validate("id=#{id}") , | |
validate("from=#{from}") , | |
validate("to=#{to}") , | |
validate("") | |
end | |
private | |
attr_accessor :file | |
# raise an error if the string is too large to fit in the file (we don't want to mismatch the line lengths | |
# because we calculate all the things by offsets, so if a line length was incorrect, then we would end up jumping | |
# to the wrong location, which would mess everything up.) | |
def validate(str) | |
raise 'string exceeds the maximum size' if str.size > LINE_WIDTH-1 | |
str | |
end | |
end | |
end | |
# the mail record class itself, gives us the functionality to interface with our file | |
class MailRecord | |
# create a new MailRecord | |
def initialize(file) | |
self.file = file | |
yield self # let the user interact through this block so that we can make sure the file gets closed | |
end | |
# returns the given record (note this is based on location in file, not id) | |
# will store it and return the same record if queried again, or will pull from file if not seen before | |
def []( record_number ) | |
records[record_number] ||= get_from_file(record_number) | |
end | |
# a nice syntax to access the objects through, see the example | |
def self.execute( filename , &block ) | |
file = File.open filename , 'r+' # open the file for reading and writing | |
MailRecord.new file , &block | |
nil | |
ensure | |
file.close # need to make sure the file gets closed or things can get messed up | |
end | |
# gives us all of these methods http://ruby-doc.org/core/classes/Enumerable.html | |
# just pulls each record until it hits the end of the file | |
include Enumerable | |
def each | |
i = 0 | |
loop do | |
record = get_from_file(i) | |
yield record | |
i += 1 | |
end | |
rescue EOFError | |
end | |
private | |
# For integrity, user should only interact with instance variables through methods we define | |
attr_accessor :file | |
# a hash of records we have pulled, stored in memory | |
def records | |
@records ||= Hash.new | |
end | |
# retrieves the record from the file | |
def get_from_file( record_number ) | |
offset = MailRecord.record_offset record_number | |
file.seek( offset , IO::SEEK_SET ) | |
Record.new file | |
end | |
# remember that we begin counting at zero not one | |
# so record_offset(2) will be the third record, not the second | |
def self.record_offset( record_number ) | |
record_number * RECORD_WIDTH | |
end | |
end | |
# ----- setup for example ----- | |
# create the file and populate it so that we have something to experiment on | |
filename = 'Mail.dat' | |
File.open filename , 'w' do |file| | |
file.puts DATA.read # pulls data from after __END__ and writes it to the file | |
end | |
# show you what it looks like before we mess with it, as a reference | |
display_file = lambda do |text| | |
puts text | |
File.readlines(filename).each { |line| puts line.inspect } # show inspected version so the fixed width is clear | |
end | |
display_file.call "#{filename} before we do anything:" | |
# ----- example of how to use ----- | |
# open the file, get the first record, change it's title, change who it was sent to, and write it back to the file | |
MailRecord.execute filename do |records| | |
# change a record | |
record = records[1] | |
record.to = 'jill' | |
record.title = 'Hi love!' | |
record.write | |
# use an iterator to pull all the titles | |
titles = records.map { |record| record.title.inspect } | |
puts "\n\nthe titles are: #{titles.join ', '}" | |
# display the record whose id is 416 | |
puts "\n\nThe record with the id of 416 looks like" | |
puts records.find { |record| record.id == 416 } | |
# use an iterator to double all of the ids | |
records.each do |record| | |
record.id *= 2 | |
record.write | |
end | |
end | |
# show you what it looks like after we messed with it | |
display_file.call "\n\n\n#{filename} after we are done:" | |
__END__ | |
hi there | |
id=12 | |
from=me | |
to=you | |
hey, pal | |
id=94 | |
from=you | |
to=me | |
pay up! | |
id=416 | |
from=us | |
to=them | |
pay up! | |
id=416 | |
from=us | |
to=them | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment