1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
|
module DBF
class Column
extend Forwardable
class LengthError < StandardError
end
class NameError < StandardError
end
attr_reader :table, :name, :type, :length, :decimal
def_delegator :type_cast_class, :type_cast
# rubocop:disable Style/MutableConstant
TYPE_CAST_CLASS = {
N: ColumnType::Number,
I: ColumnType::SignedLong,
F: ColumnType::Float,
Y: ColumnType::Currency,
D: ColumnType::Date,
T: ColumnType::DateTime,
L: ColumnType::Boolean,
M: ColumnType::Memo,
B: ColumnType::Double,
G: ColumnType::General,
'+'.to_sym => ColumnType::SignedLong2
}
# rubocop:enable Style/MutableConstant
TYPE_CAST_CLASS.default = ColumnType::String
TYPE_CAST_CLASS.freeze
# Initialize a new DBF::Column
#
# @param table [String]
# @param name [String]
# @param type [String]
# @param length [Integer]
# @param decimal [Integer]
def initialize(table, name, type, length, decimal)
@table = table
@name = clean(name)
@type = type
@length = length
@decimal = decimal
@version = table.version
@encoding = table.encoding
validate_length
validate_name
end
# Returns true if the column is a memo
#
# @return [Boolean]
def memo?
@memo ||= type == 'M'
end
# Returns a Hash with :name, :type, :length, and :decimal keys
#
# @return [Hash]
def to_hash
{name: name, type: type, length: length, decimal: decimal}
end
# Underscored name
#
# This is the column name converted to underscore format.
# For example, MyColumn will be returned as my_column.
#
# @return [String]
def underscored_name
@underscored_name ||= name.gsub(/([a-z\d])([A-Z])/, '\1_\2').tr('-', '_').downcase
end
private
def clean(value) # :nodoc:
value.strip.partition("\x00").first.gsub(/[^\x20-\x7E]/, '')
end
def encode(value, strip_output: false) # :nodoc:
return value unless value.respond_to?(:encoding)
output = @encoding ? encode_string(value) : value
strip_output ? output.strip : output
end
def encoding_args # :nodoc:
@encoding_args ||= [
Encoding.default_external,
{undef: :replace, invalid: :replace}
]
end
def encode_string(string) # :nodoc:
string.force_encoding(@encoding).encode(*encoding_args)
end
def type_cast_class # :nodoc:
@type_cast_class ||= begin
klass = @length == 0 ? ColumnType::Nil : TYPE_CAST_CLASS[type.to_sym]
klass.new(@decimal, @encoding)
end
end
def validate_length # :nodoc:
raise LengthError, 'field length must be 0 or greater' if length < 0
end
def validate_name # :nodoc:
raise NameError, 'column name cannot be empty' if @name.empty?
end
end
end
|