dbf 3.1.1 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  module DBF
2
2
  module Memo
3
3
  class Dbase3 < Base
4
- def build_memo(start_block) # nodoc
4
+ def build_memo(start_block) # :nodoc:
5
5
  @data.seek offset(start_block)
6
6
  memo_string = ''
7
7
  loop do
@@ -1,7 +1,7 @@
1
1
  module DBF
2
2
  module Memo
3
3
  class Dbase4 < Base
4
- def build_memo(start_block) # nodoc
4
+ def build_memo(start_block) # :nodoc:
5
5
  @data.seek offset(start_block)
6
6
  @data.read(@data.read(BLOCK_HEADER_SIZE).unpack('x4L').first)
7
7
  end
@@ -3,7 +3,7 @@ module DBF
3
3
  class Foxpro < Base
4
4
  FPT_HEADER_SIZE = 512
5
5
 
6
- def build_memo(start_block) # nodoc
6
+ def build_memo(start_block) # :nodoc:
7
7
  @data.seek offset(start_block)
8
8
 
9
9
  memo_type, memo_size, memo_string = @data.read(block_size).unpack('NNa*')
@@ -22,7 +22,7 @@ module DBF
22
22
 
23
23
  private
24
24
 
25
- def block_size # nodoc
25
+ def block_size # :nodoc:
26
26
  @block_size ||= begin
27
27
  @data.rewind
28
28
  @data.read(FPT_HEADER_SIZE).unpack('x6n').first || 0
@@ -49,15 +49,6 @@ module DBF
49
49
  options.all? { |key, value| self[key] == value }
50
50
  end
51
51
 
52
- # Overrides standard Object.respond_to? to return true if a
53
- # matching column name is found.
54
- #
55
- # @param [String, Symbol] method
56
- # @return [Boolean]
57
- def respond_to?(method, *args)
58
- underscored_column_names.include?(method.to_s) || super
59
- end
60
-
61
52
  # Maps a row to an array of values
62
53
  #
63
54
  # @return [Array]
@@ -67,15 +58,15 @@ module DBF
67
58
 
68
59
  private
69
60
 
70
- def attribute_map # nodoc
61
+ def attribute_map # :nodoc:
71
62
  @columns.map { |column| [column.name, init_attribute(column)] }
72
63
  end
73
64
 
74
- def get_data(column) # nodoc
65
+ def get_data(column) # :nodoc:
75
66
  @data.read(column.length)
76
67
  end
77
68
 
78
- def get_memo(column) # nodoc
69
+ def get_memo(column) # :nodoc:
79
70
  if @memo
80
71
  @memo.get(memo_start_block(column))
81
72
  else
@@ -85,20 +76,18 @@ module DBF
85
76
  end
86
77
  end
87
78
 
88
- def init_attribute(column) # nodoc
79
+ def init_attribute(column) # :nodoc:
89
80
  value = column.memo? ? get_memo(column) : get_data(column)
90
81
  column.type_cast(value)
91
82
  end
92
83
 
93
- def memo_start_block(column) # nodoc
84
+ def memo_start_block(column) # :nodoc:
94
85
  data = get_data(column)
95
- if %w(30 31).include?(@version)
96
- data = data.unpack('V').first
97
- end
86
+ data = data.unpack('V').first if %w[30 31].include?(@version)
98
87
  data.to_i
99
88
  end
100
89
 
101
- def method_missing(method, *args) # nodoc
90
+ def method_missing(method, *args) # :nodoc:
102
91
  if (index = underscored_column_names.index(method.to_s))
103
92
  attributes[@columns[index].name]
104
93
  else
@@ -106,7 +95,11 @@ module DBF
106
95
  end
107
96
  end
108
97
 
109
- def underscored_column_names # nodoc
98
+ def respond_to_missing?(method, *) # :nodoc:
99
+ underscored_column_names.include?(method.to_s) || super
100
+ end
101
+
102
+ def underscored_column_names # :nodoc:
110
103
  @underscored_column_names ||= @columns.map(&:underscored_name)
111
104
  end
112
105
  end
@@ -1,6 +1,17 @@
1
1
  module DBF
2
2
  # The Schema module is mixin for the Table class
3
3
  module Schema
4
+ FORMATS = [:activerecord, :json, :sequel].freeze
5
+
6
+ OTHER_DATA_TYPES = {
7
+ 'Y' => ':decimal, :precision => 15, :scale => 4',
8
+ 'D' => ':date',
9
+ 'T' => ':datetime',
10
+ 'L' => ':boolean',
11
+ 'M' => ':text',
12
+ 'B' => ':binary'
13
+ }.freeze
14
+
4
15
  # Generate an ActiveRecord::Schema
5
16
  #
6
17
  # xBase data types are converted to generic types as follows:
@@ -24,15 +35,17 @@ module DBF
24
35
  # @param [Symbol] format Valid options are :activerecord and :json
25
36
  # @return [String]
26
37
  def schema(format = :activerecord, table_only = false)
27
- supported_formats = [:activerecord, :json, :sequel]
28
- if supported_formats.include?(format)
29
- send("#{format}_schema", table_only)
30
- else
31
- raise ArgumentError
32
- end
38
+ schema_method_name = schema_name(format)
39
+ send(schema_method_name, table_only)
40
+ rescue NameError
41
+ raise ArgumentError, ":#{format} is not a valid schema. Valid schemas are: #{FORMATS.join(', ')}."
42
+ end
43
+
44
+ def schema_name(format) # :nodoc:
45
+ "#{format}_schema"
33
46
  end
34
47
 
35
- def activerecord_schema(_table_only = false) # nodoc
48
+ def activerecord_schema(_table_only = false) # :nodoc:
36
49
  s = "ActiveRecord::Schema.define do\n"
37
50
  s << " create_table \"#{name}\" do |t|\n"
38
51
  columns.each do |column|
@@ -42,7 +55,7 @@ module DBF
42
55
  s
43
56
  end
44
57
 
45
- def sequel_schema(table_only = false) # nodoc
58
+ def sequel_schema(table_only = false) # :nodoc:
46
59
  s = ''
47
60
  s << "Sequel.migration do\n" unless table_only
48
61
  s << " change do\n " unless table_only
@@ -51,12 +64,12 @@ module DBF
51
64
  s << " column #{sequel_schema_definition(column)}"
52
65
  end
53
66
  s << " end\n"
54
- s << " end\n" unless table_only
55
- s << "end\n" unless table_only
67
+ s << " end\n" unless table_only
68
+ s << "end\n" unless table_only
56
69
  s
57
70
  end
58
71
 
59
- def json_schema(_table_only = false) # nodoc
72
+ def json_schema(_table_only = false) # :nodoc:
60
73
  columns.map(&:to_hash).to_json
61
74
  end
62
75
 
@@ -76,30 +89,26 @@ module DBF
76
89
  ":#{column.underscored_name}, #{schema_data_type(column, :sequel)}\n"
77
90
  end
78
91
 
79
- def schema_data_type(column, format = :activerecord) # nodoc
92
+ def schema_data_type(column, format = :activerecord) # :nodoc:
80
93
  case column.type
81
- when 'N', 'F'
82
- column.decimal > 0 ? ':float' : ':integer'
83
- when 'I'
84
- ':integer'
85
- when 'Y'
86
- ':decimal, :precision => 15, :scale => 4'
87
- when 'D'
88
- ':date'
89
- when 'T'
90
- ':datetime'
91
- when 'L'
92
- ':boolean'
93
- when 'M'
94
- ':text'
95
- when 'B'
96
- ':binary'
94
+ when *%w[N F I]
95
+ number_data_type(column)
96
+ when *%w[Y D T L M B]
97
+ OTHER_DATA_TYPES[column.type]
98
+ else
99
+ string_data_format(format, column)
100
+ end
101
+ end
102
+
103
+ def number_data_type(column)
104
+ column.decimal > 0 ? ':float' : ':integer'
105
+ end
106
+
107
+ def string_data_format(format, column)
108
+ if format == :sequel
109
+ ":varchar, :size => #{column.length}"
97
110
  else
98
- if format == :sequel
99
- ":varchar, :size => #{column.length}"
100
- else
101
- ":string, :limit => #{column.length}"
102
- end
111
+ ":string, :limit => #{column.length}"
103
112
  end
104
113
  end
105
114
  end
@@ -28,14 +28,14 @@ module DBF
28
28
  'cb' => 'dBASE IV SQL table files, with memo',
29
29
  'f5' => 'FoxPro with memo file',
30
30
  'fb' => 'FoxPro without memo file'
31
- }
31
+ }.freeze
32
32
 
33
33
  FOXPRO_VERSIONS = {
34
34
  '30' => 'Visual FoxPro',
35
35
  '31' => 'Visual FoxPro with AutoIncrement field',
36
36
  'f5' => 'FoxPro with memo file',
37
37
  'fb' => 'FoxPro without memo file'
38
- }
38
+ }.freeze
39
39
 
40
40
  attr_accessor :encoding
41
41
  attr_writer :name
@@ -157,7 +157,7 @@ module DBF
157
157
 
158
158
  # @return [String]
159
159
  def name
160
- @name ||= filename && File.basename(filename, ".*")
160
+ @name ||= filename && File.basename(filename, '.*')
161
161
  end
162
162
 
163
163
  # Retrieve a record by index number.
@@ -172,7 +172,7 @@ module DBF
172
172
  DBF::Record.new(@data.read(header.record_length), columns, version, @memo)
173
173
  end
174
174
 
175
- alias_method :row, :record
175
+ alias row record
176
176
 
177
177
  # Total number of records
178
178
  #
@@ -208,30 +208,29 @@ module DBF
208
208
 
209
209
  private
210
210
 
211
- def build_columns # nodoc
212
- @data.seek(DBF_HEADER_SIZE)
213
- columns = []
214
- until end_of_record?
215
- column_data = @data.read(DBF_HEADER_SIZE)
216
- name, type, length, decimal = column_data.unpack('a10 x a x4 C2')
217
- columns << Column.new(self, name, type, length, decimal)
211
+ def build_columns # :nodoc:
212
+ safe_seek do
213
+ @data.seek(DBF_HEADER_SIZE)
214
+ columns = []
215
+ until end_of_record?
216
+ column_data = @data.read(DBF_HEADER_SIZE)
217
+ name, type, length, decimal = column_data.unpack('a10 x a x4 C2')
218
+ columns << Column.new(self, name, type, length, decimal)
219
+ end
220
+ columns
218
221
  end
219
- columns
220
222
  end
221
223
 
222
- def deleted_record? # nodoc
224
+ def deleted_record? # :nodoc:
223
225
  flag = @data.read(1)
224
226
  flag ? flag.unpack('a') == ['*'] : true
225
227
  end
226
228
 
227
- def end_of_record? # nodoc
228
- original_pos = @data.pos
229
- byte = @data.read(1)
230
- @data.seek(original_pos)
231
- byte.ord == 13
229
+ def end_of_record? # :nodoc:
230
+ safe_seek { @data.read(1).ord == 13 }
232
231
  end
233
232
 
234
- def find_all(options) # nodoc
233
+ def find_all(options) # :nodoc:
235
234
  map do |record|
236
235
  if record && record.match?(options)
237
236
  yield record if block_given?
@@ -240,19 +239,22 @@ module DBF
240
239
  end.compact
241
240
  end
242
241
 
243
- def find_first(options) # nodoc
242
+ def find_first(options) # :nodoc:
244
243
  detect { |record| record && record.match?(options) }
245
244
  end
246
245
 
247
- def foxpro? # nodoc
246
+ def foxpro? # :nodoc:
248
247
  FOXPRO_VERSIONS.keys.include? version
249
248
  end
250
249
 
251
- def header
252
- @header ||= Header.new(@data.read DBF_HEADER_SIZE)
250
+ def header # :nodoc:
251
+ @header ||= safe_seek do
252
+ @data.seek(0)
253
+ Header.new(@data.read DBF_HEADER_SIZE)
254
+ end
253
255
  end
254
256
 
255
- def memo_class # nodoc
257
+ def memo_class # :nodoc:
256
258
  @memo_class ||= begin
257
259
  if foxpro?
258
260
  Memo::Foxpro
@@ -262,33 +264,38 @@ module DBF
262
264
  end
263
265
  end
264
266
 
265
- def memo_search_path(io) # nodoc
267
+ def memo_search_path(io) # :nodoc:
266
268
  dirname = File.dirname(io)
267
269
  basename = File.basename(io, '.*')
268
270
  "#{dirname}/#{basename}*.{fpt,FPT,dbt,DBT}"
269
271
  end
270
272
 
271
- def open_data(data) # nodoc
273
+ def open_data(data) # :nodoc:
272
274
  data.is_a?(StringIO) ? data : File.open(data, 'rb')
273
275
  rescue Errno::ENOENT
274
276
  raise DBF::FileNotFoundError, "file not found: #{data}"
275
277
  end
276
278
 
277
- def open_memo(data, memo = nil) # nodoc
279
+ def open_memo(data, memo = nil) # :nodoc:
278
280
  if memo
279
281
  meth = memo.is_a?(StringIO) ? :new : :open
280
282
  memo_class.send(meth, memo, version)
281
283
  elsif !data.is_a?(StringIO)
282
- files = Dir.glob(memo_search_path data)
284
+ files = Dir.glob(memo_search_path(data))
283
285
  files.any? ? memo_class.open(files.first, version) : nil
284
286
  end
285
287
  end
286
288
 
287
- def seek(offset) # nodoc
289
+ def safe_seek # :nodoc:
290
+ original_pos = @data.pos
291
+ yield.tap { @data.seek(original_pos) }
292
+ end
293
+
294
+ def seek(offset) # :nodoc:
288
295
  @data.seek(header.header_length + offset)
289
296
  end
290
297
 
291
- def seek_to_record(index) # nodoc
298
+ def seek_to_record(index) # :nodoc:
292
299
  seek(index * header.record_length)
293
300
  end
294
301
  end
@@ -1,3 +1,3 @@
1
1
  module DBF
2
- VERSION = '3.1.1'
2
+ VERSION = '3.1.2'
3
3
  end
@@ -3,7 +3,7 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe DBF::Column do
6
- let(:table) { DBF::Table.new fixture('dbase_30.dbf')}
6
+ let(:table) { DBF::Table.new fixture('dbase_30.dbf') }
7
7
 
8
8
  context 'when initialized' do
9
9
  let(:column) { DBF::Column.new table, 'ColumnName', 'N', 1, 0 }
@@ -19,13 +19,13 @@ RSpec.describe DBF::Database::Foxpro do
19
19
 
20
20
  describe 'it loads the example db correctly' do
21
21
  it 'shows a correct list of tables' do
22
- expect(db.table_names.sort).to eq(%w(contacts calls setup types).sort)
22
+ expect(db.table_names.sort).to eq(%w[contacts calls setup types].sort)
23
23
  end
24
24
  end
25
25
  end
26
26
 
27
27
  describe '#table' do
28
- describe "when accessing related tables" do
28
+ describe 'when accessing related tables' do
29
29
  let(:db) { DBF::Database::Foxpro.new(dbf_path) }
30
30
 
31
31
  it 'loads an existing related table' do
@@ -1,167 +1,167 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  RSpec.shared_examples_for 'DBF' do
4
- specify "sum of column lengths should equal record length specified in header plus one" do
5
- header_record_length = table.instance_eval {@header.record_length}
6
- sum_of_column_lengths = table.columns.inject(1) {|sum, column| sum += column.length}
4
+ specify 'sum of column lengths should equal record length specified in header plus one' do
5
+ header_record_length = table.instance_eval { @header.record_length }
6
+ sum_of_column_lengths = table.columns.inject(1) { |sum, column| sum += column.length }
7
7
 
8
8
  expect(header_record_length).to eq sum_of_column_lengths
9
9
  end
10
10
 
11
- specify "records should be instances of DBF::Record" do
11
+ specify 'records should be instances of DBF::Record' do
12
12
  table.each do |record|
13
13
  expect(record).to be_kind_of(DBF::Record)
14
14
  end
15
15
  end
16
16
 
17
- specify "record count should be the same as reported in the header" do
17
+ specify 'record count should be the same as reported in the header' do
18
18
  expect(table.entries.size).to eq table.record_count
19
19
  end
20
20
 
21
- specify "column names should not be blank" do
21
+ specify 'column names should not be blank' do
22
22
  table.columns.each do |column|
23
23
  expect(column.name).not_to be_empty
24
24
  end
25
25
  end
26
26
 
27
- specify "column types should be valid" do
28
- valid_column_types = %w(C N L D M F B G P Y T I V X @ O + 0)
27
+ specify 'column types should be valid' do
28
+ valid_column_types = %w[C N L D M F B G P Y T I V X @ O + 0]
29
29
  table.columns.each do |column|
30
30
  expect(valid_column_types).to include(column.type)
31
31
  end
32
32
  end
33
33
 
34
- specify "column lengths should be instances of Integer" do
34
+ specify 'column lengths should be instances of Integer' do
35
35
  table.columns.each do |column|
36
36
  expect(column.length).to be_kind_of(Integer)
37
37
  end
38
38
  end
39
39
 
40
- specify "column lengths should be larger than 0" do
40
+ specify 'column lengths should be larger than 0' do
41
41
  table.columns.each do |column|
42
42
  expect(column.length).to be > 0
43
43
  end
44
44
  end
45
45
 
46
- specify "column decimals should be instances of Integer" do
46
+ specify 'column decimals should be instances of Integer' do
47
47
  table.columns.each do |column|
48
48
  expect(column.decimal).to be_kind_of(Integer)
49
49
  end
50
50
  end
51
51
  end
52
52
 
53
- RSpec.describe DBF, "of type 03 (dBase III without memo file)" do
53
+ RSpec.describe DBF, 'of type 03 (dBase III without memo file)' do
54
54
  let(:table) { DBF::Table.new fixture('dbase_03.dbf') }
55
55
 
56
- it_should_behave_like "DBF"
56
+ it_should_behave_like 'DBF'
57
57
 
58
- it "should report the correct version number" do
59
- expect(table.version).to eq "03"
58
+ it 'should report the correct version number' do
59
+ expect(table.version).to eq '03'
60
60
  end
61
61
 
62
- it "should report the correct version description" do
63
- expect(table.version_description).to eq "dBase III without memo file"
62
+ it 'should report the correct version description' do
63
+ expect(table.version_description).to eq 'dBase III without memo file'
64
64
  end
65
65
 
66
- it "should determine the number of records" do
66
+ it 'should determine the number of records' do
67
67
  expect(table.record_count).to eq 14
68
68
  end
69
69
  end
70
70
 
71
- RSpec.describe DBF, "of type 30 (Visual FoxPro)" do
71
+ RSpec.describe DBF, 'of type 30 (Visual FoxPro)' do
72
72
  let(:table) { DBF::Table.new fixture('dbase_30.dbf') }
73
73
 
74
- it_should_behave_like "DBF"
74
+ it_should_behave_like 'DBF'
75
75
 
76
- it "should report the correct version number" do
77
- expect(table.version).to eq "30"
76
+ it 'should report the correct version number' do
77
+ expect(table.version).to eq '30'
78
78
  end
79
79
 
80
- it "should report the correct version description" do
81
- expect(table.version_description).to eq "Visual FoxPro"
80
+ it 'should report the correct version description' do
81
+ expect(table.version_description).to eq 'Visual FoxPro'
82
82
  end
83
83
 
84
- it "should determine the number of records" do
84
+ it 'should determine the number of records' do
85
85
  expect(table.record_count).to eq 34
86
86
  end
87
87
 
88
- it "reads memo data" do
88
+ it 'reads memo data' do
89
89
  expect(table.record(3).classes).to match(/\AAgriculture.*Farming\r\n\Z/m)
90
90
  end
91
91
  end
92
92
 
93
- RSpec.describe DBF, "of type 31 (Visual FoxPro with AutoIncrement field)" do
93
+ RSpec.describe DBF, 'of type 31 (Visual FoxPro with AutoIncrement field)' do
94
94
  let(:table) { DBF::Table.new fixture('dbase_31.dbf') }
95
95
 
96
- it_should_behave_like "DBF"
96
+ it_should_behave_like 'DBF'
97
97
 
98
- it "should have a dBase version of 31" do
99
- expect(table.version).to eq "31"
98
+ it 'should have a dBase version of 31' do
99
+ expect(table.version).to eq '31'
100
100
  end
101
101
 
102
- it "should report the correct version description" do
103
- expect(table.version_description).to eq "Visual FoxPro with AutoIncrement field"
102
+ it 'should report the correct version description' do
103
+ expect(table.version_description).to eq 'Visual FoxPro with AutoIncrement field'
104
104
  end
105
105
 
106
- it "should determine the number of records" do
106
+ it 'should determine the number of records' do
107
107
  expect(table.record_count).to eq 77
108
108
  end
109
109
  end
110
110
 
111
- RSpec.describe DBF, "of type 83 (dBase III with memo file)" do
111
+ RSpec.describe DBF, 'of type 83 (dBase III with memo file)' do
112
112
  let(:table) { DBF::Table.new fixture('dbase_83.dbf') }
113
113
 
114
- it_should_behave_like "DBF"
114
+ it_should_behave_like 'DBF'
115
115
 
116
- it "should report the correct version number" do
117
- expect(table.version).to eq "83"
116
+ it 'should report the correct version number' do
117
+ expect(table.version).to eq '83'
118
118
  end
119
119
 
120
- it "should report the correct version description" do
121
- expect(table.version_description).to eq "dBase III with memo file"
120
+ it 'should report the correct version description' do
121
+ expect(table.version_description).to eq 'dBase III with memo file'
122
122
  end
123
123
 
124
- it "should determine the number of records" do
124
+ it 'should determine the number of records' do
125
125
  expect(table.record_count).to eq 67
126
126
  end
127
127
  end
128
128
 
129
- RSpec.describe DBF, "of type 8b (dBase IV with memo file)" do
129
+ RSpec.describe DBF, 'of type 8b (dBase IV with memo file)' do
130
130
  let(:table) { DBF::Table.new fixture('dbase_8b.dbf') }
131
131
 
132
- it_should_behave_like "DBF"
132
+ it_should_behave_like 'DBF'
133
133
 
134
- it "should report the correct version number" do
135
- expect(table.version).to eq "8b"
134
+ it 'should report the correct version number' do
135
+ expect(table.version).to eq '8b'
136
136
  end
137
137
 
138
- it "should report the correct version description" do
139
- expect(table.version_description).to eq "dBase IV with memo file"
138
+ it 'should report the correct version description' do
139
+ expect(table.version_description).to eq 'dBase IV with memo file'
140
140
  end
141
141
 
142
- it "should determine the number of records" do
142
+ it 'should determine the number of records' do
143
143
  expect(table.record_count).to eq 10
144
144
  end
145
145
  end
146
146
 
147
- RSpec.describe DBF, "of type f5 (FoxPro with memo file)" do
147
+ RSpec.describe DBF, 'of type f5 (FoxPro with memo file)' do
148
148
  let(:table) { DBF::Table.new fixture('dbase_f5.dbf') }
149
149
 
150
- it_should_behave_like "DBF"
150
+ it_should_behave_like 'DBF'
151
151
 
152
- it "should report the correct version number" do
153
- expect(table.version).to eq "f5"
152
+ it 'should report the correct version number' do
153
+ expect(table.version).to eq 'f5'
154
154
  end
155
155
 
156
- it "should report the correct version description" do
157
- expect(table.version_description).to eq "FoxPro with memo file"
156
+ it 'should report the correct version description' do
157
+ expect(table.version_description).to eq 'FoxPro with memo file'
158
158
  end
159
159
 
160
- it "should determine the number of records" do
160
+ it 'should determine the number of records' do
161
161
  expect(table.record_count).to eq 975
162
162
  end
163
163
 
164
- it "reads memo data" do
164
+ it 'reads memo data' do
165
165
  expect(table.record(3).datn.to_s).to eq '1870-06-30'
166
166
  end
167
167
  end