lib/dbf/table.rb in dbf-2.0.7 vs lib/dbf/table.rb in dbf-2.0.8

- old
+ new

@@ -1,11 +1,14 @@ module DBF + class FileNotFoundError < StandardError + end # DBF::Table is the primary interface to a single DBF file and provides # methods for enumerating and searching the records. class Table include Enumerable + include Schema DBF_HEADER_SIZE = 32 VERSIONS = { "02" => "FoxBase", @@ -57,15 +60,19 @@ # # @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), supports_encoding? || supports_iconv?) - @encoding = encoding || header.encoding - @memo = open_memo(data, memo) + begin + @data = open_data(data) + @data.rewind + @header = Header.new(@data.read(DBF_HEADER_SIZE), supports_encoding?) + @encoding = encoding || header.encoding + @memo = open_memo(data, memo) + rescue Errno::ENOENT => error + raise DBF::FileNotFoundError.new("file not found: #{data}") + end end # @return [TrueClass, FalseClass] def has_memo_file? !!@memo @@ -106,11 +113,11 @@ # the database. # # @param [Fixnum] index # @return [DBF::Record, NilClass] def record(index) - seek(index * header.record_length) + seek_to_record(index) if !deleted_record? DBF::Record.new(@data.read(header.record_length), columns, version, @memo) end end @@ -135,41 +142,10 @@ # @return [String] def version_description VERSIONS[version] end - # Generate an ActiveRecord::Schema - # - # xBase data types are converted to generic types as follows: - # - Number columns with no decimals are converted to :integer - # - Number columns with decimals are converted to :float - # - Date columns are converted to :datetime - # - Logical columns are converted to :boolean - # - Memo columns are converted to :text - # - Character columns are converted to :string and the :limit option is set - # to the length of the character column - # - # Example: - # create_table "mydata" do |t| - # t.column :name, :string, :limit => 30 - # t.column :last_update, :datetime - # t.column :is_active, :boolean - # t.column :age, :integer - # t.column :notes, :text - # end - # - # @return [String] - def schema - s = "ActiveRecord::Schema.define do\n" - s << " create_table \"#{File.basename(@data.path, ".*")}\" do |t|\n" - columns.each do |column| - s << " t.column #{column.schema_definition}" - end - s << " end\nend" - s - 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) @@ -213,43 +189,63 @@ when :first find_first(options) end end - # Retrieves column information from the database + # All columns + # + # @return [Array] def columns - @columns ||= begin - @data.seek(DBF_HEADER_SIZE) - columns = [] - while !["\0", "\r"].include?(first_byte = @data.read(1)) - column_data = first_byte + @data.read(31) - name, type, length, decimal = column_data.unpack('a10 x a x4 C2') - if length > 0 - columns << column_class.new(name.strip, type, length, decimal, version, encoding) - end - end - columns - end + @columns ||= build_columns end + # Column names + # + # @return [String] + def column_names + columns.map { |column| column.name } + end + + # Is string encoding supported? + # String encoding is always supported in Ruby 1.9+. + # Ruby 1.8.x requires that Ruby be compiled with iconv support. def supports_encoding? - String.new.respond_to?(:encoding) + supports_string_encoding? || supports_iconv? end - def supports_iconv? + # Does String support encoding? Should be true in Ruby 1.9+ + def supports_string_encoding? + ''.respond_to?(:encoding) + end + + def supports_iconv? #nodoc require 'iconv' true rescue false end - def foxpro? - FOXPRO_VERSIONS.keys.include? version + private + + def build_columns #nodoc + columns = [] + @data.seek(DBF_HEADER_SIZE) + while !["\0", "\r"].include?(first_byte = @data.read(1)) + column_data = first_byte + @data.read(DBF_HEADER_SIZE - 1) + name, type, length, decimal = column_data.unpack('a10 x a x4 C2') + if length > 0 + columns << column_class.new(self, name, type, length, decimal) + end + end + columns end - private + def foxpro? #nodoc + FOXPRO_VERSIONS.keys.include? version + end + def column_class #nodoc @column_class ||= foxpro? ? Column::Foxpro : Column::Dbase end def memo_class #nodoc @@ -308,9 +304,13 @@ @data.read(1).unpack('a') == ['*'] end def seek(offset) #nodoc @data.seek header.header_length + offset + end + + def seek_to_record(index) #nodoc + seek(index * header.record_length) end def csv_class #nodoc @csv_class ||= CSV.const_defined?(:Reader) ? FCSV : CSV end