dbf 5.4.0 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -0
- data/LICENSE +1 -1
- data/README.md +91 -39
- data/dbf.gemspec +5 -1
- data/lib/dbf/cli.rb +82 -16
- data/lib/dbf/column.rb +28 -5
- data/lib/dbf/column_builder.rb +38 -2
- data/lib/dbf/column_type.rb +11 -4
- data/lib/dbf/database/foxpro.rb +23 -15
- data/lib/dbf/encoder.rb +60 -5
- data/lib/dbf/errors.rb +37 -0
- data/lib/dbf/file_handler.rb +62 -14
- data/lib/dbf/header.rb +1 -3
- data/lib/dbf/memo/base.rb +15 -4
- data/lib/dbf/memo/dbase3.rb +18 -4
- data/lib/dbf/memo/dbase4.rb +1 -1
- data/lib/dbf/memo/foxpro.rb +15 -14
- data/lib/dbf/record.rb +18 -5
- data/lib/dbf/record_context.rb +2 -1
- data/lib/dbf/record_iterator.rb +49 -20
- data/lib/dbf/schema.rb +3 -4
- data/lib/dbf/table.rb +64 -19
- data/lib/dbf/version.rb +1 -1
- data/lib/dbf.rb +1 -0
- metadata +11 -6
data/lib/dbf/table.rb
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module DBF
|
|
4
|
-
class FileNotFoundError < StandardError
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
class NoColumnsDefined < StandardError
|
|
8
|
-
end
|
|
9
|
-
|
|
10
4
|
# Leading bytes that make a spreadsheet treat a CSV cell as a formula:
|
|
11
5
|
# "=", "+", "-", "@", tab, and carriage return.
|
|
12
6
|
CSV_FORMULA_TRIGGERS = [0x3D, 0x2B, 0x2D, 0x40, 0x09, 0x0D].freeze
|
|
@@ -37,6 +31,9 @@ module DBF
|
|
|
37
31
|
# # working with a dbf in memory
|
|
38
32
|
# table = DBF::Table.new StringIO.new(dbf_data)
|
|
39
33
|
#
|
|
34
|
+
# # working with an open IO object
|
|
35
|
+
# table = DBF::Table.new File.open('data.dbf', 'rb')
|
|
36
|
+
#
|
|
40
37
|
# # working with a dbf and memo in memory
|
|
41
38
|
# table = DBF::Table.new StringIO.new(dbf_data), StringIO.new(memo_data)
|
|
42
39
|
#
|
|
@@ -44,14 +41,41 @@ module DBF
|
|
|
44
41
|
# table = DBF::Table.new 'data.dbf', nil, 'cp437'
|
|
45
42
|
# table = DBF::Table.new 'data.dbf', 'memo.dbt', Encoding::US_ASCII
|
|
46
43
|
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
44
|
+
# # reading at most the first 64 KB of each memo
|
|
45
|
+
# table = DBF::Table.new 'data.dbf', max_memo_size: 65_536
|
|
46
|
+
#
|
|
47
|
+
# Opens a table like .new, but when given a block, yields the table,
|
|
48
|
+
# closes it when the block returns, and returns the block's value —
|
|
49
|
+
# the same contract as File.open.
|
|
50
|
+
#
|
|
51
|
+
# DBF::Table.open('data.dbf') do |table|
|
|
52
|
+
# table.each { |record| ... }
|
|
53
|
+
# end
|
|
54
|
+
#
|
|
55
|
+
# Takes the same arguments as .new. Without a block, equivalent to .new.
|
|
56
|
+
def self.open(data, memo = nil, encoding = nil, name: nil, max_memo_size: nil)
|
|
57
|
+
table = new(data, memo, encoding, name:, max_memo_size:)
|
|
58
|
+
return table unless block_given?
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
yield table
|
|
62
|
+
ensure
|
|
63
|
+
table.close
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @param data [String, StringIO, IO] data Path to the dbf file or an IO-like object
|
|
68
|
+
# @param memo [optional String, StringIO, IO] memo Path to the memo file or an IO-like object
|
|
49
69
|
# @param encoding [optional String, Encoding] encoding Name of the encoding or an Encoding object
|
|
50
|
-
|
|
70
|
+
# @param max_memo_size [optional Integer] Bytes to read from each memo;
|
|
71
|
+
# longer memos are cut off. Every memo is read in full by default.
|
|
72
|
+
def initialize(data, memo = nil, encoding = nil, name: nil, max_memo_size: nil)
|
|
73
|
+
Encoder.check(encoding) if encoding
|
|
74
|
+
validate_max_memo_size(max_memo_size)
|
|
51
75
|
@data = FileHandler.open_data(data)
|
|
52
76
|
@user_encoding = encoding
|
|
53
77
|
@encoding = determine_encoding
|
|
54
|
-
@memo =
|
|
78
|
+
@memo = open_memo(data, memo, max_memo_size)
|
|
55
79
|
@name = name
|
|
56
80
|
yield self if block_given?
|
|
57
81
|
end
|
|
@@ -110,7 +134,7 @@ module DBF
|
|
|
110
134
|
|
|
111
135
|
# @return [String]
|
|
112
136
|
def filename
|
|
113
|
-
File.basename(@data.path) if @data.
|
|
137
|
+
File.basename(@data.path) if @data.respond_to?(:path)
|
|
114
138
|
end
|
|
115
139
|
|
|
116
140
|
# @return [TrueClass, FalseClass]
|
|
@@ -131,6 +155,9 @@ module DBF
|
|
|
131
155
|
# @return [DBF::Record, NilClass]
|
|
132
156
|
def record(index)
|
|
133
157
|
raise DBF::NoColumnsDefined, 'The DBF file has no columns defined' if columns.empty?
|
|
158
|
+
# A negative index would read header bytes as a record (or raise
|
|
159
|
+
# Errno::EINVAL); treat it, like any index past the end, as absent.
|
|
160
|
+
return nil unless index >= 0 && index < record_count
|
|
134
161
|
|
|
135
162
|
seek_to_record(index)
|
|
136
163
|
return nil if deleted_record?
|
|
@@ -150,14 +177,11 @@ module DBF
|
|
|
150
177
|
#
|
|
151
178
|
# @param [optional String, IO] path_or_io String path, IO-like object, or nil for STDOUT
|
|
152
179
|
def to_csv(path_or_io = nil)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
180
|
+
if path_or_io.is_a?(String)
|
|
181
|
+
File.open(path_or_io, 'w') { |file| write_csv(file) }
|
|
182
|
+
else
|
|
183
|
+
write_csv(path_or_io || $stdout)
|
|
157
184
|
end
|
|
158
|
-
csv = CSV.new(io, force_quotes: true)
|
|
159
|
-
csv << column_names.map { |name| csv_safe_value(name) }
|
|
160
|
-
each { |record| csv << record.to_a.map { |value| csv_safe_value(value) } }
|
|
161
185
|
end
|
|
162
186
|
|
|
163
187
|
# Human readable version description
|
|
@@ -184,6 +208,12 @@ module DBF
|
|
|
184
208
|
|
|
185
209
|
private
|
|
186
210
|
|
|
211
|
+
def write_csv(io) # :nodoc:
|
|
212
|
+
csv = CSV.new(io, force_quotes: true)
|
|
213
|
+
csv << column_names.map { |name| csv_safe_value(name) }
|
|
214
|
+
each { |record| csv << record.to_a.map { |value| csv_safe_value(value) } }
|
|
215
|
+
end
|
|
216
|
+
|
|
187
217
|
# Neutralizes spreadsheet formula injection (CWE-1236) on CSV export by
|
|
188
218
|
# prefixing a single quote to string cells that begin with a formula
|
|
189
219
|
# trigger character. Non-string values (numbers, dates, booleans) are
|
|
@@ -217,8 +247,23 @@ module DBF
|
|
|
217
247
|
@user_encoding || header.encoding || Encoding.default_external
|
|
218
248
|
end
|
|
219
249
|
|
|
250
|
+
# Close a data file opened from a path if its memo cannot be opened, so
|
|
251
|
+
# the failed Table does not leak a file handle.
|
|
252
|
+
def open_memo(data, memo, max_size) # :nodoc:
|
|
253
|
+
FileHandler.open_memo(data, memo, version_config.memo_class, version, max_size:)
|
|
254
|
+
rescue DBF::Error
|
|
255
|
+
@data.close if data.is_a?(String)
|
|
256
|
+
raise
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def validate_max_memo_size(max_memo_size) # :nodoc:
|
|
260
|
+
return if max_memo_size.nil? || (max_memo_size.is_a?(Integer) && max_memo_size.positive?)
|
|
261
|
+
|
|
262
|
+
raise ArgumentError, 'max_memo_size must be a positive Integer or nil'
|
|
263
|
+
end
|
|
264
|
+
|
|
220
265
|
def build_columns # :nodoc:
|
|
221
|
-
ColumnBuilder.new(self, @data, version_config).build
|
|
266
|
+
ColumnBuilder.new(self, @data, version_config, header_length:, record_length:).build
|
|
222
267
|
end
|
|
223
268
|
|
|
224
269
|
def deleted_record? # :nodoc:
|
data/lib/dbf/version.rb
CHANGED
data/lib/dbf.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dbf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keith Morrison
|
|
@@ -13,16 +13,16 @@ dependencies:
|
|
|
13
13
|
name: csv
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '3.3'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '3.3'
|
|
26
26
|
description: A small fast library for reading dBase, xBase, Clipper and FoxPro database
|
|
27
27
|
files.
|
|
28
28
|
email: keithm@infused.org
|
|
@@ -44,6 +44,7 @@ files:
|
|
|
44
44
|
- lib/dbf/database/foxpro.rb
|
|
45
45
|
- lib/dbf/encoder.rb
|
|
46
46
|
- lib/dbf/encodings.rb
|
|
47
|
+
- lib/dbf/errors.rb
|
|
47
48
|
- lib/dbf/file_handler.rb
|
|
48
49
|
- lib/dbf/find.rb
|
|
49
50
|
- lib/dbf/header.rb
|
|
@@ -63,8 +64,12 @@ licenses:
|
|
|
63
64
|
- MIT
|
|
64
65
|
metadata:
|
|
65
66
|
rubygems_mfa_required: 'true'
|
|
67
|
+
homepage_uri: https://github.com/infused/dbf
|
|
66
68
|
source_code_uri: https://github.com/infused/dbf
|
|
67
69
|
changelog_uri: https://github.com/infused/dbf/blob/main/CHANGELOG.md
|
|
70
|
+
bug_tracker_uri: https://github.com/infused/dbf/issues
|
|
71
|
+
documentation_uri: https://dbf.infused.org
|
|
72
|
+
funding_uri: https://github.com/sponsors/infused
|
|
68
73
|
rdoc_options: []
|
|
69
74
|
require_paths:
|
|
70
75
|
- lib
|
|
@@ -79,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
79
84
|
- !ruby/object:Gem::Version
|
|
80
85
|
version: '0'
|
|
81
86
|
requirements: []
|
|
82
|
-
rubygems_version: 4.0.
|
|
87
|
+
rubygems_version: 4.0.20
|
|
83
88
|
specification_version: 4
|
|
84
89
|
summary: Read xBase files
|
|
85
90
|
test_files: []
|