lib/dbf/table.rb in dbf-3.1.0 vs lib/dbf/table.rb in dbf-3.1.1
- old
+ new
@@ -35,11 +35,10 @@
'31' => 'Visual FoxPro with AutoIncrement field',
'f5' => 'FoxPro with memo file',
'fb' => 'FoxPro without memo file'
}
- attr_reader :header
attr_accessor :encoding
attr_writer :name
# Opens a DBF::Table
# Examples:
@@ -62,24 +61,15 @@
# @param [String, StringIO] data Path to the dbf file or a StringIO object
# @param [optional String, StringIO] memo Path to the memo file or a StringIO object
# @param [optional String, Encoding] encoding Name of the encoding or an Encoding object
def initialize(data, memo = nil, encoding = nil)
@data = open_data(data)
- @data.rewind
- @header = Header.new(@data.read DBF_HEADER_SIZE)
@encoding = encoding || header.encoding
@memo = open_memo(data, memo)
yield self if block_given?
- rescue Errno::ENOENT
- raise DBF::FileNotFoundError, "file not found: #{data}"
end
- # @return [TrueClass, FalseClass]
- def has_memo_file?
- !!@memo
- end
-
# Closes the table and memo file
#
# @return [TrueClass, FalseClass]
def close
@data.close
@@ -93,74 +83,37 @@
else
@data.closed?
end
end
- # @return String
- def filename
- File.basename @data.path if @data.respond_to?(:path)
+ # Column names
+ #
+ # @return [String]
+ def column_names
+ columns.map(&:name)
end
- # @return String
- def name
- @name ||= filename && File.basename(filename, ".*")
+ # All columns
+ #
+ # @return [Array]
+ def columns
+ @columns ||= build_columns
end
# Calls block once for each record in the table. The record may be nil
# if the record has been marked as deleted.
#
# @yield [nil, DBF::Record]
def each
header.record_count.times { |i| yield record(i) }
end
- # Retrieve a record by index number.
- # The record will be nil if it has been deleted, but not yet pruned from
- # the database.
- #
- # @param [Fixnum] index
- # @return [DBF::Record, NilClass]
- def record(index)
- seek_to_record(index)
- return nil if deleted_record?
- DBF::Record.new(@data.read(header.record_length), columns, version, @memo)
- end
-
- alias_method :row, :record
-
- # Internal dBase version number
- #
# @return [String]
- def version
- @version ||= header.version
+ def filename
+ File.basename(@data.path) if @data.respond_to?(:path)
end
- # Total number of records
- #
- # @return [Fixnum]
- def record_count
- @record_count ||= header.record_count
- end
-
- # Human readable version description
- #
- # @return [String]
- def version_description
- VERSIONS[version]
- end
-
- # Dumps all records to a CSV file. If no filename is given then CSV is
- # output to STDOUT.
- #
- # @param [optional String] path Defaults to STDOUT
- def to_csv(path = nil)
- out_io = path ? File.open(path, 'w') : $stdout
- csv = CSV.new(out_io, force_quotes: true)
- csv << column_names
- each { |record| csv << record.to_a }
- end
-
# Find records using a simple ActiveRecord-like syntax.
#
# Examples:
# table = DBF::Table.new 'mydata.dbf'
#
@@ -179,40 +132,82 @@
# matched exactly with the value in the database. If you specify more
# than one key, all values must match in order for the record to be
# returned. The equivalent SQL would be "WHERE key1 = 'value1'
# AND key2 = 'value2'".
#
- # @param [Fixnum, Symbol] command
+ # @param [Integer, Symbol] command
# @param [optional, Hash] options Hash of search parameters
# @yield [optional, DBF::Record, NilClass]
def find(command, options = {}, &block)
case command
- when Fixnum
+ when Integer
record(command)
when Array
command.map { |i| record(i) }
when :all
find_all(options, &block)
when :first
find_first(options)
end
end
- # All columns
+ # @return [TrueClass, FalseClass]
+ def has_memo_file?
+ !!@memo
+ end
+
+ # @return [String]
+ def name
+ @name ||= filename && File.basename(filename, ".*")
+ end
+
+ # Retrieve a record by index number.
+ # The record will be nil if it has been deleted, but not yet pruned from
+ # the database.
#
- # @return [Array]
- def columns
- @columns ||= build_columns
+ # @param [Integer] index
+ # @return [DBF::Record, NilClass]
+ def record(index)
+ seek_to_record(index)
+ return nil if deleted_record?
+ DBF::Record.new(@data.read(header.record_length), columns, version, @memo)
end
- # Column names
+ alias_method :row, :record
+
+ # Total number of records
#
+ # @return [Integer]
+ def record_count
+ @record_count ||= header.record_count
+ end
+
+ # Dumps all records to a CSV file. If no filename is given then CSV is
+ # output to STDOUT.
+ #
+ # @param [optional String] path Defaults to STDOUT
+ def to_csv(path = nil)
+ out_io = path ? File.open(path, 'w') : $stdout
+ csv = CSV.new(out_io, force_quotes: true)
+ csv << column_names
+ each { |record| csv << record.to_a }
+ end
+
+ # Internal dBase version number
+ #
# @return [String]
- def column_names
- columns.map(&:name)
+ def version
+ @version ||= header.version
end
+ # Human readable version description
+ #
+ # @return [String]
+ def version_description
+ VERSIONS[version]
+ end
+
private
def build_columns # nodoc
@data.seek(DBF_HEADER_SIZE)
columns = []
@@ -222,33 +217,63 @@
columns << Column.new(self, name, type, length, decimal)
end
columns
end
+ def deleted_record? # nodoc
+ flag = @data.read(1)
+ flag ? flag.unpack('a') == ['*'] : true
+ end
+
def end_of_record? # nodoc
original_pos = @data.pos
byte = @data.read(1)
@data.seek(original_pos)
byte.ord == 13
end
+ def find_all(options) # nodoc
+ map do |record|
+ if record && record.match?(options)
+ yield record if block_given?
+ record
+ end
+ end.compact
+ end
+
+ def find_first(options) # nodoc
+ detect { |record| record && record.match?(options) }
+ end
+
def foxpro? # nodoc
FOXPRO_VERSIONS.keys.include? version
end
+ def header
+ @header ||= Header.new(@data.read DBF_HEADER_SIZE)
+ end
+
def memo_class # nodoc
@memo_class ||= begin
if foxpro?
Memo::Foxpro
else
version == '83' ? Memo::Dbase3 : Memo::Dbase4
end
end
end
+ def memo_search_path(io) # nodoc
+ dirname = File.dirname(io)
+ basename = File.basename(io, '.*')
+ "#{dirname}/#{basename}*.{fpt,FPT,dbt,DBT}"
+ end
+
def open_data(data) # nodoc
data.is_a?(StringIO) ? data : File.open(data, 'rb')
+ rescue Errno::ENOENT
+ raise DBF::FileNotFoundError, "file not found: #{data}"
end
def open_memo(data, memo = nil) # nodoc
if memo
meth = memo.is_a?(StringIO) ? :new : :open
@@ -257,35 +282,11 @@
files = Dir.glob(memo_search_path data)
files.any? ? memo_class.open(files.first, version) : nil
end
end
- def memo_search_path(io) # nodoc
- dirname = File.dirname(io)
- basename = File.basename(io, '.*')
- "#{dirname}/#{basename}*.{fpt,FPT,dbt,DBT}"
- end
-
- def find_all(options) # nodoc
- map do |record|
- if record && record.match?(options)
- yield record if block_given?
- record
- end
- end.compact
- end
-
- def find_first(options) # nodoc
- detect { |record| record && record.match?(options) }
- end
-
- def deleted_record? # nodoc
- flag = @data.read(1)
- flag ? flag.unpack('a') == ['*'] : true
- end
-
def seek(offset) # nodoc
- @data.seek header.header_length + offset
+ @data.seek(header.header_length + offset)
end
def seek_to_record(index) # nodoc
seek(index * header.record_length)
end