dbf 3.1.3 → 4.1.3
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 +15 -0
- data/Gemfile +5 -1
- data/Gemfile.lock +33 -25
- data/Guardfile +1 -1
- data/LICENSE +1 -1
- data/README.md +9 -6
- data/bin/dbf +3 -3
- data/dbf.gemspec +2 -1
- data/docs/supported_types.markdown +16 -31
- data/lib/dbf.rb +3 -2
- data/lib/dbf/column.rb +3 -8
- data/lib/dbf/column_type.rb +12 -17
- data/lib/dbf/database/foxpro.rb +1 -3
- data/lib/dbf/memo/base.rb +1 -0
- data/lib/dbf/memo/dbase4.rb +1 -1
- data/lib/dbf/memo/foxpro.rb +2 -3
- data/lib/dbf/record.rb +1 -1
- data/lib/dbf/table.rb +23 -21
- data/lib/dbf/version.rb +1 -1
- data/spec/dbf/column_spec.rb +23 -23
- data/spec/dbf/{database_spec.rb → database/foxpro_spec.rb} +0 -0
- data/spec/dbf/file_formats_spec.rb +29 -31
- data/spec/dbf/record_spec.rb +7 -7
- data/spec/dbf/table_spec.rb +25 -23
- data/spec/fixtures/polygon.dbf +0 -0
- data/spec/spec_helper.rb +0 -2
- metadata +10 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4c2ff24f150ee6567de0532c773695f8b47c0db811660e0f5f3be3d53a7cc053
|
|
4
|
+
data.tar.gz: 7006275aff71326833452546657bb07520d53024b6846b0c982121a488cd8235
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2d4bffa8201a8fa07abf642e721523e7ed59bb2b3e9a0fae31fe5ce6a4a3078f1520eb09e350d979dd88a68801c7c52d3236c402f83a41da1e5e6c4bca39911
|
|
7
|
+
data.tar.gz: fddf6622338b52b4bb6006513a92d9fd44c8f1e864738fd597d7e9f3fc21bcd86707f5e68e76216bd4c6d3be7dad9a48e5e19d55a6728691f6d90d86ec6db25e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# 4.1.3
|
|
2
|
+
- Raise DBF::NoColumnsDefined error when attempting to read records if no columns are defined
|
|
3
|
+
|
|
4
|
+
# 4.1.1
|
|
5
|
+
- Add required_ruby_version to gemspec
|
|
6
|
+
|
|
7
|
+
# 4.1.0
|
|
8
|
+
- Return Time instead of DateTime
|
|
9
|
+
|
|
10
|
+
# 4.0.0
|
|
11
|
+
- Drop support for ruby-2.2 and earlier
|
|
12
|
+
|
|
13
|
+
# 3.1.3
|
|
14
|
+
- Ensure malformed dates return nil
|
|
15
|
+
|
|
1
16
|
# 3.1.2
|
|
2
17
|
- Fix incorrect columns list when StringIO and encoding set
|
|
3
18
|
|
data/Gemfile
CHANGED
|
@@ -4,9 +4,13 @@ source 'https://rubygems.org'
|
|
|
4
4
|
group :development, :test do
|
|
5
5
|
gem 'awesome_print'
|
|
6
6
|
gem 'byebug'
|
|
7
|
+
gem 'e2mmap'
|
|
7
8
|
gem 'guard'
|
|
8
9
|
gem 'guard-rspec'
|
|
10
|
+
gem 'irb'
|
|
9
11
|
gem 'rake'
|
|
10
|
-
gem 'rubocop'
|
|
11
12
|
gem 'rspec'
|
|
13
|
+
gem 'rubocop'
|
|
14
|
+
gem 'rubocop-performance'
|
|
15
|
+
gem 'rubocop-rspec'
|
|
12
16
|
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
dbf (
|
|
4
|
+
dbf (4.1.3)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
9
|
ast (2.4.0)
|
|
10
10
|
awesome_print (1.8.0)
|
|
11
|
-
byebug (
|
|
11
|
+
byebug (11.0.1)
|
|
12
12
|
coderay (1.1.2)
|
|
13
13
|
diff-lcs (1.3)
|
|
14
|
-
|
|
14
|
+
e2mmap (0.1.0)
|
|
15
|
+
ffi (1.11.1)
|
|
15
16
|
formatador (0.2.5)
|
|
16
|
-
guard (2.
|
|
17
|
+
guard (2.15.0)
|
|
17
18
|
formatador (>= 0.2.4)
|
|
18
19
|
listen (>= 2.7, < 4.0)
|
|
19
20
|
lumberjack (>= 1.0.12, < 2.0)
|
|
@@ -27,55 +28,58 @@ GEM
|
|
|
27
28
|
guard (~> 2.1)
|
|
28
29
|
guard-compat (~> 1.1)
|
|
29
30
|
rspec (>= 2.99.0, < 4.0)
|
|
30
|
-
|
|
31
|
+
irb (1.0.0)
|
|
32
|
+
jaro_winkler (1.5.3)
|
|
31
33
|
listen (3.1.5)
|
|
32
34
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
|
33
35
|
rb-inotify (~> 0.9, >= 0.9.7)
|
|
34
36
|
ruby_dep (~> 1.2)
|
|
35
37
|
lumberjack (1.0.13)
|
|
36
|
-
method_source (0.9.
|
|
38
|
+
method_source (0.9.2)
|
|
37
39
|
nenv (0.3.0)
|
|
38
40
|
notiffany (0.1.1)
|
|
39
41
|
nenv (~> 0.1)
|
|
40
42
|
shellany (~> 0.0)
|
|
41
|
-
parallel (1.
|
|
42
|
-
parser (2.
|
|
43
|
+
parallel (1.17.0)
|
|
44
|
+
parser (2.6.3.0)
|
|
43
45
|
ast (~> 2.4.0)
|
|
44
|
-
|
|
45
|
-
pry (0.11.3)
|
|
46
|
+
pry (0.12.2)
|
|
46
47
|
coderay (~> 1.1.0)
|
|
47
48
|
method_source (~> 0.9.0)
|
|
48
49
|
rainbow (3.0.0)
|
|
49
|
-
rake (12.3.
|
|
50
|
+
rake (12.3.2)
|
|
50
51
|
rb-fsevent (0.10.3)
|
|
51
|
-
rb-inotify (0.
|
|
52
|
-
ffi (
|
|
52
|
+
rb-inotify (0.10.0)
|
|
53
|
+
ffi (~> 1.0)
|
|
53
54
|
rspec (3.8.0)
|
|
54
55
|
rspec-core (~> 3.8.0)
|
|
55
56
|
rspec-expectations (~> 3.8.0)
|
|
56
57
|
rspec-mocks (~> 3.8.0)
|
|
57
|
-
rspec-core (3.8.
|
|
58
|
+
rspec-core (3.8.2)
|
|
58
59
|
rspec-support (~> 3.8.0)
|
|
59
|
-
rspec-expectations (3.8.
|
|
60
|
+
rspec-expectations (3.8.4)
|
|
60
61
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
61
62
|
rspec-support (~> 3.8.0)
|
|
62
|
-
rspec-mocks (3.8.
|
|
63
|
+
rspec-mocks (3.8.1)
|
|
63
64
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
64
65
|
rspec-support (~> 3.8.0)
|
|
65
|
-
rspec-support (3.8.
|
|
66
|
-
rubocop (0.
|
|
66
|
+
rspec-support (3.8.2)
|
|
67
|
+
rubocop (0.73.0)
|
|
67
68
|
jaro_winkler (~> 1.5.1)
|
|
68
69
|
parallel (~> 1.10)
|
|
69
|
-
parser (>= 2.
|
|
70
|
-
powerpack (~> 0.1)
|
|
70
|
+
parser (>= 2.6)
|
|
71
71
|
rainbow (>= 2.2.2, < 4.0)
|
|
72
72
|
ruby-progressbar (~> 1.7)
|
|
73
|
-
unicode-display_width (
|
|
74
|
-
|
|
73
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
|
74
|
+
rubocop-performance (1.4.0)
|
|
75
|
+
rubocop (>= 0.71.0)
|
|
76
|
+
rubocop-rspec (1.33.0)
|
|
77
|
+
rubocop (>= 0.60.0)
|
|
78
|
+
ruby-progressbar (1.10.1)
|
|
75
79
|
ruby_dep (1.5.0)
|
|
76
80
|
shellany (0.0.1)
|
|
77
|
-
thor (0.20.
|
|
78
|
-
unicode-display_width (1.
|
|
81
|
+
thor (0.20.3)
|
|
82
|
+
unicode-display_width (1.6.0)
|
|
79
83
|
|
|
80
84
|
PLATFORMS
|
|
81
85
|
ruby
|
|
@@ -84,11 +88,15 @@ DEPENDENCIES
|
|
|
84
88
|
awesome_print
|
|
85
89
|
byebug
|
|
86
90
|
dbf!
|
|
91
|
+
e2mmap
|
|
87
92
|
guard
|
|
88
93
|
guard-rspec
|
|
94
|
+
irb
|
|
89
95
|
rake
|
|
90
96
|
rspec
|
|
91
97
|
rubocop
|
|
98
|
+
rubocop-performance
|
|
99
|
+
rubocop-rspec
|
|
92
100
|
|
|
93
101
|
BUNDLED WITH
|
|
94
|
-
1.
|
|
102
|
+
2.1.4
|
data/Guardfile
CHANGED
|
@@ -6,6 +6,6 @@ guard :rspec, cmd: 'bundle exec rspec' do
|
|
|
6
6
|
# watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
|
7
7
|
watch(%r{^lib/(.+)\.rb$}) { 'spec' }
|
|
8
8
|
watch('spec/spec_helper.rb') { 'spec' }
|
|
9
|
-
watch(/
|
|
9
|
+
watch(%r{spec/fixtures/(.+)}) { 'spec' }
|
|
10
10
|
watch('Guardfile') { 'spec' }
|
|
11
11
|
end
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -16,14 +16,17 @@ database files
|
|
|
16
16
|
subject line
|
|
17
17
|
* Change log: <https://github.com/infused/dbf/blob/master/CHANGELOG.md>
|
|
18
18
|
|
|
19
|
-
NOTE:
|
|
19
|
+
NOTE: Beginning with version 4 we have dropped support for Ruby 2.0, 2.1, 2.2, and 2.3. If you need support for these older Rubies,
|
|
20
|
+
please use 3.0.x (https://github.com/infused.org/dbf/tree/3_stable)
|
|
21
|
+
|
|
22
|
+
NOTE: Beginning with version 3 we have dropped support for Ruby 1.8 and 1.9. If you need support for older Rubies,
|
|
23
|
+
please use 2.0.x (https://github.com/infused/dbf/tree/2_stable)
|
|
20
24
|
|
|
21
25
|
## Compatibility
|
|
22
26
|
|
|
23
27
|
DBF is tested to work with the following versions of Ruby:
|
|
24
28
|
|
|
25
|
-
*
|
|
26
|
-
* JRuby 1.7.x
|
|
29
|
+
* Ruby 2.4.x, 2.5.x, 2.6.x, 2.7.x
|
|
27
30
|
|
|
28
31
|
## Installation
|
|
29
32
|
|
|
@@ -208,7 +211,7 @@ Sequel.migration do
|
|
|
208
211
|
end
|
|
209
212
|
```
|
|
210
213
|
|
|
211
|
-
If you have
|
|
214
|
+
If you have initialized the DBF::Table with raw data, you will need to set the
|
|
212
215
|
exported table name manually:
|
|
213
216
|
|
|
214
217
|
```ruby
|
|
@@ -226,7 +229,7 @@ A small command-line utility called dbf is installed along with the gem.
|
|
|
226
229
|
-s = print summary information
|
|
227
230
|
-a = create an ActiveRecord::Schema
|
|
228
231
|
-r = create a Sequel Migration
|
|
229
|
-
-c =
|
|
232
|
+
-c = export as CSV
|
|
230
233
|
|
|
231
234
|
Create an executable ActiveRecord schema:
|
|
232
235
|
|
|
@@ -272,7 +275,7 @@ for a full list of supported column types.
|
|
|
272
275
|
|
|
273
276
|
## License
|
|
274
277
|
|
|
275
|
-
Copyright (c) 2006-
|
|
278
|
+
Copyright (c) 2006-2020 Keith Morrison <<keithm@infused.org>>
|
|
276
279
|
|
|
277
280
|
Permission is hereby granted, free of charge, to any person
|
|
278
281
|
obtaining a copy of this software and associated documentation
|
data/bin/dbf
CHANGED
|
@@ -9,14 +9,14 @@ params = ARGV.getopts('h', 's', 'a', 'c', 'r', 'v')
|
|
|
9
9
|
if params['v']
|
|
10
10
|
puts "dbf version: #{DBF::VERSION}"
|
|
11
11
|
|
|
12
|
-
elsif params['h']
|
|
12
|
+
elsif params['h']
|
|
13
13
|
puts "usage: #{File.basename(__FILE__)} [-h|-s|-a|-c|-r] filename"
|
|
14
14
|
puts ' -h = print this message'
|
|
15
15
|
puts ' -v = print the DBF gem version'
|
|
16
16
|
puts ' -s = print summary information'
|
|
17
17
|
puts ' -a = create an ActiveRecord::Schema'
|
|
18
18
|
puts ' -r = create a Sequel migration'
|
|
19
|
-
puts ' -c =
|
|
19
|
+
puts ' -c = export as CSV'
|
|
20
20
|
else
|
|
21
21
|
|
|
22
22
|
filename = ARGV.shift
|
|
@@ -46,7 +46,7 @@ else
|
|
|
46
46
|
puts 'Name Type Length Decimal'
|
|
47
47
|
puts '-' * 78
|
|
48
48
|
table.columns.each do |f|
|
|
49
|
-
puts '%-16s %-10s %-10s %-10s'
|
|
49
|
+
puts format('%-16s %-10s %-10s %-10s', f.name, f.type, f.length, f.decimal)
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
data/dbf.gemspec
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
lib = File.expand_path('
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
|
2
2
|
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
|
|
3
3
|
require 'dbf/version'
|
|
4
4
|
|
|
@@ -19,4 +19,5 @@ Gem::Specification.new do |s|
|
|
|
19
19
|
s.test_files = Dir.glob('spec/**/*_spec.rb')
|
|
20
20
|
s.require_paths = ['lib']
|
|
21
21
|
s.required_rubygems_version = Gem::Requirement.new('>= 1.3.0')
|
|
22
|
+
s.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
|
|
22
23
|
end
|
|
@@ -1,36 +1,21 @@
|
|
|
1
1
|
# DBF supported data types
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
20
|
-
| 7b | dBase IV with memo file | Y | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - |
|
|
21
|
-
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
22
|
-
| 83 | dBase III with memo file | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
23
|
-
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
24
|
-
| 87 | Visual Objects 1.x with memo file | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
25
|
-
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
26
|
-
| 8b | dBase IV with memo file | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | N | - | - | - |
|
|
27
|
-
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
28
|
-
| 8e | dBase IV with SQL table | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | N | - | - | - |
|
|
29
|
-
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
30
|
-
| f5 | FoxPro with memo file | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
|
|
31
|
-
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
32
|
-
| fb | FoxPro without memo file | Y | Y | Y | Y | - | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
|
|
33
|
-
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
3
|
+
| Version | Description | C | N | L | D | M | F | B | G | P | Y | T | I | V | X | @ | O | + |
|
|
4
|
+
|---------|-----------------------------------|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
5
|
+
| 02 | FoxBase | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
6
|
+
| 03 | dBase III without memo file | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
7
|
+
| 04 | dBase IV without memo file | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
8
|
+
| 05 | dBase V without memo file | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
9
|
+
| 07 | Visual Objects 1.x | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
10
|
+
| 30 | Visual FoxPro | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | - |
|
|
11
|
+
| 31 | Visual FoxPro with AutoIncrement | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
|
|
12
|
+
| 7b | dBase IV with memo file | Y | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - |
|
|
13
|
+
| 83 | dBase III with memo file | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
14
|
+
| 87 | Visual Objects 1.x with memo file | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - |
|
|
15
|
+
| 8b | dBase IV with memo file | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | N | - | - | - |
|
|
16
|
+
| 8e | dBase IV with SQL table | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | N | - | - | - |
|
|
17
|
+
| f5 | FoxPro with memo file | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
|
|
18
|
+
| fb | FoxPro without memo file | Y | Y | Y | Y | - | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
|
|
34
19
|
|
|
35
20
|
Data type descriptions
|
|
36
21
|
|
data/lib/dbf.rb
CHANGED
data/lib/dbf/column.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module DBF
|
|
2
2
|
class Column
|
|
3
|
+
extend Forwardable
|
|
4
|
+
|
|
3
5
|
class LengthError < StandardError
|
|
4
6
|
end
|
|
5
7
|
|
|
@@ -7,6 +9,7 @@ module DBF
|
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
attr_reader :table, :name, :type, :length, :decimal
|
|
12
|
+
def_delegator :type_cast_class, :type_cast
|
|
10
13
|
|
|
11
14
|
TYPE_CAST_CLASS = {
|
|
12
15
|
N: ColumnType::Number,
|
|
@@ -56,14 +59,6 @@ module DBF
|
|
|
56
59
|
{name: name, type: type, length: length, decimal: decimal}
|
|
57
60
|
end
|
|
58
61
|
|
|
59
|
-
# Cast value to native type
|
|
60
|
-
#
|
|
61
|
-
# @param [String] value
|
|
62
|
-
# @return [Integer, Float, Date, DateTime, Boolean, String]
|
|
63
|
-
def type_cast(value)
|
|
64
|
-
type_cast_class.type_cast(value)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
62
|
# Underscored name
|
|
68
63
|
#
|
|
69
64
|
# This is the column name converted to underscore format.
|
data/lib/dbf/column_type.rb
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
module DBF
|
|
2
2
|
module ColumnType
|
|
3
3
|
class Base
|
|
4
|
-
ENCODING_ARGS = [
|
|
5
|
-
Encoding.default_external,
|
|
6
|
-
{undef: :replace, invalid: :replace}
|
|
7
|
-
].freeze
|
|
8
|
-
|
|
9
4
|
attr_reader :decimal, :encoding
|
|
10
5
|
|
|
11
6
|
def initialize(decimal, encoding)
|
|
@@ -15,7 +10,7 @@ module DBF
|
|
|
15
10
|
end
|
|
16
11
|
|
|
17
12
|
class Nil < Base
|
|
18
|
-
def type_cast(
|
|
13
|
+
def type_cast(_value)
|
|
19
14
|
nil
|
|
20
15
|
end
|
|
21
16
|
end
|
|
@@ -23,19 +18,20 @@ module DBF
|
|
|
23
18
|
class Number < Base
|
|
24
19
|
def type_cast(value)
|
|
25
20
|
return nil if value.strip.empty?
|
|
21
|
+
|
|
26
22
|
@decimal.zero? ? value.to_i : value.to_f
|
|
27
23
|
end
|
|
28
24
|
end
|
|
29
25
|
|
|
30
26
|
class Currency < Base
|
|
31
27
|
def type_cast(value)
|
|
32
|
-
(value.
|
|
28
|
+
(value.unpack1('q<') / 10_000.0).to_f
|
|
33
29
|
end
|
|
34
30
|
end
|
|
35
31
|
|
|
36
32
|
class SignedLong < Base
|
|
37
33
|
def type_cast(value)
|
|
38
|
-
value.
|
|
34
|
+
value.unpack1('l<')
|
|
39
35
|
end
|
|
40
36
|
end
|
|
41
37
|
|
|
@@ -47,20 +43,20 @@ module DBF
|
|
|
47
43
|
|
|
48
44
|
class Double < Base
|
|
49
45
|
def type_cast(value)
|
|
50
|
-
value.
|
|
46
|
+
value.unpack1('E')
|
|
51
47
|
end
|
|
52
48
|
end
|
|
53
49
|
|
|
54
50
|
class Boolean < Base
|
|
55
51
|
def type_cast(value)
|
|
56
|
-
value.strip
|
|
52
|
+
value.strip.match?(/^(y|t)$/i)
|
|
57
53
|
end
|
|
58
54
|
end
|
|
59
55
|
|
|
60
56
|
class Date < Base
|
|
61
57
|
def type_cast(value)
|
|
62
|
-
value
|
|
63
|
-
rescue
|
|
58
|
+
value.match?(/\d{8}/) && ::Date.strptime(value, '%Y%m%d')
|
|
59
|
+
rescue StandardError
|
|
64
60
|
nil
|
|
65
61
|
end
|
|
66
62
|
end
|
|
@@ -69,8 +65,8 @@ module DBF
|
|
|
69
65
|
def type_cast(value)
|
|
70
66
|
days, msecs = value.unpack('l2')
|
|
71
67
|
secs = (msecs / 1000).to_i
|
|
72
|
-
::DateTime.jd(days, (secs / 3600).to_i, (secs / 60).to_i % 60, secs % 60)
|
|
73
|
-
rescue
|
|
68
|
+
::DateTime.jd(days, (secs / 3600).to_i, (secs / 60).to_i % 60, secs % 60).to_time
|
|
69
|
+
rescue StandardError
|
|
74
70
|
nil
|
|
75
71
|
end
|
|
76
72
|
end
|
|
@@ -78,7 +74,7 @@ module DBF
|
|
|
78
74
|
class Memo < Base
|
|
79
75
|
def type_cast(value)
|
|
80
76
|
if encoding && !value.nil?
|
|
81
|
-
value.force_encoding(@encoding).encode(
|
|
77
|
+
value.force_encoding(@encoding).encode(Encoding.default_external, undef: :replace, invalid: :replace)
|
|
82
78
|
else
|
|
83
79
|
value
|
|
84
80
|
end
|
|
@@ -94,9 +90,8 @@ module DBF
|
|
|
94
90
|
class String < Base
|
|
95
91
|
def type_cast(value)
|
|
96
92
|
value = value.strip
|
|
97
|
-
@encoding ? value.force_encoding(@encoding).encode(
|
|
93
|
+
@encoding ? value.force_encoding(@encoding).encode(Encoding.default_external, undef: :replace, invalid: :replace) : value
|
|
98
94
|
end
|
|
99
95
|
end
|
|
100
|
-
|
|
101
96
|
end
|
|
102
97
|
end
|
data/lib/dbf/database/foxpro.rb
CHANGED
|
@@ -45,9 +45,7 @@ module DBF
|
|
|
45
45
|
glob = File.join(@dirname, "#{name}.dbf")
|
|
46
46
|
path = Dir.glob(glob, File::FNM_CASEFOLD).first
|
|
47
47
|
|
|
48
|
-
unless path && File.exist?(path)
|
|
49
|
-
raise DBF::FileNotFoundError, "related table not found: #{name}"
|
|
50
|
-
end
|
|
48
|
+
raise DBF::FileNotFoundError, "related table not found: #{name}" unless path && File.exist?(path)
|
|
51
49
|
|
|
52
50
|
path
|
|
53
51
|
end
|
data/lib/dbf/memo/base.rb
CHANGED
data/lib/dbf/memo/dbase4.rb
CHANGED
data/lib/dbf/memo/foxpro.rb
CHANGED
|
@@ -15,8 +15,7 @@ module DBF
|
|
|
15
15
|
memo_string = memo_string[0, memo_size]
|
|
16
16
|
end
|
|
17
17
|
memo_string
|
|
18
|
-
|
|
19
|
-
rescue
|
|
18
|
+
rescue StandardError
|
|
20
19
|
nil
|
|
21
20
|
end
|
|
22
21
|
|
|
@@ -25,7 +24,7 @@ module DBF
|
|
|
25
24
|
def block_size # :nodoc:
|
|
26
25
|
@block_size ||= begin
|
|
27
26
|
@data.rewind
|
|
28
|
-
@data.read(FPT_HEADER_SIZE).
|
|
27
|
+
@data.read(FPT_HEADER_SIZE).unpack1('x6n') || 0
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
30
|
end
|
data/lib/dbf/record.rb
CHANGED
data/lib/dbf/table.rb
CHANGED
|
@@ -2,9 +2,13 @@ module DBF
|
|
|
2
2
|
class FileNotFoundError < StandardError
|
|
3
3
|
end
|
|
4
4
|
|
|
5
|
+
class NoColumnsDefined < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
5
8
|
# DBF::Table is the primary interface to a single DBF file and provides
|
|
6
9
|
# methods for enumerating and searching the records.
|
|
7
10
|
class Table
|
|
11
|
+
extend Forwardable
|
|
8
12
|
include Enumerable
|
|
9
13
|
include ::DBF::Schema
|
|
10
14
|
|
|
@@ -40,6 +44,11 @@ module DBF
|
|
|
40
44
|
attr_accessor :encoding
|
|
41
45
|
attr_writer :name
|
|
42
46
|
|
|
47
|
+
def_delegator :header, :header_length
|
|
48
|
+
def_delegator :header, :record_count
|
|
49
|
+
def_delegator :header, :record_length
|
|
50
|
+
def_delegator :header, :version
|
|
51
|
+
|
|
43
52
|
# Opens a DBF::Table
|
|
44
53
|
# Examples:
|
|
45
54
|
# # working with a file stored on the filesystem
|
|
@@ -104,12 +113,14 @@ module DBF
|
|
|
104
113
|
#
|
|
105
114
|
# @yield [nil, DBF::Record]
|
|
106
115
|
def each
|
|
107
|
-
|
|
116
|
+
record_count.times { |i| yield record(i) }
|
|
108
117
|
end
|
|
109
118
|
|
|
110
119
|
# @return [String]
|
|
111
120
|
def filename
|
|
112
|
-
|
|
121
|
+
return unless @data.respond_to?(:path)
|
|
122
|
+
|
|
123
|
+
File.basename(@data.path)
|
|
113
124
|
end
|
|
114
125
|
|
|
115
126
|
# Find records using a simple ActiveRecord-like syntax.
|
|
@@ -167,20 +178,17 @@ module DBF
|
|
|
167
178
|
# @param [Integer] index
|
|
168
179
|
# @return [DBF::Record, NilClass]
|
|
169
180
|
def record(index)
|
|
181
|
+
raise DBF::NoColumnsDefined, 'The DBF file has no columns defined' if columns.empty?
|
|
182
|
+
|
|
170
183
|
seek_to_record(index)
|
|
171
184
|
return nil if deleted_record?
|
|
172
|
-
|
|
185
|
+
|
|
186
|
+
record_data = @data.read(record_length)
|
|
187
|
+
DBF::Record.new(record_data, columns, version, @memo)
|
|
173
188
|
end
|
|
174
189
|
|
|
175
190
|
alias row record
|
|
176
191
|
|
|
177
|
-
# Total number of records
|
|
178
|
-
#
|
|
179
|
-
# @return [Integer]
|
|
180
|
-
def record_count
|
|
181
|
-
@record_count ||= header.record_count
|
|
182
|
-
end
|
|
183
|
-
|
|
184
192
|
# Dumps all records to a CSV file. If no filename is given then CSV is
|
|
185
193
|
# output to STDOUT.
|
|
186
194
|
#
|
|
@@ -192,13 +200,6 @@ module DBF
|
|
|
192
200
|
each { |record| csv << record.to_a }
|
|
193
201
|
end
|
|
194
202
|
|
|
195
|
-
# Internal dBase version number
|
|
196
|
-
#
|
|
197
|
-
# @return [String]
|
|
198
|
-
def version
|
|
199
|
-
@version ||= header.version
|
|
200
|
-
end
|
|
201
|
-
|
|
202
203
|
# Human readable version description
|
|
203
204
|
#
|
|
204
205
|
# @return [String]
|
|
@@ -222,7 +223,7 @@ module DBF
|
|
|
222
223
|
|
|
223
224
|
def deleted_record? # :nodoc:
|
|
224
225
|
flag = @data.read(1)
|
|
225
|
-
flag ? flag.
|
|
226
|
+
flag ? flag.unpack1('a') == '*' : true
|
|
226
227
|
end
|
|
227
228
|
|
|
228
229
|
def end_of_record? # :nodoc:
|
|
@@ -232,6 +233,7 @@ module DBF
|
|
|
232
233
|
def find_all(options) # :nodoc:
|
|
233
234
|
select do |record|
|
|
234
235
|
next unless record && record.match?(options)
|
|
236
|
+
|
|
235
237
|
yield record if block_given?
|
|
236
238
|
record
|
|
237
239
|
end
|
|
@@ -242,7 +244,7 @@ module DBF
|
|
|
242
244
|
end
|
|
243
245
|
|
|
244
246
|
def foxpro? # :nodoc:
|
|
245
|
-
FOXPRO_VERSIONS.
|
|
247
|
+
FOXPRO_VERSIONS.key?(version)
|
|
246
248
|
end
|
|
247
249
|
|
|
248
250
|
def header # :nodoc:
|
|
@@ -290,11 +292,11 @@ module DBF
|
|
|
290
292
|
end
|
|
291
293
|
|
|
292
294
|
def seek(offset) # :nodoc:
|
|
293
|
-
@data.seek(
|
|
295
|
+
@data.seek(header_length + offset)
|
|
294
296
|
end
|
|
295
297
|
|
|
296
298
|
def seek_to_record(index) # :nodoc:
|
|
297
|
-
seek(index *
|
|
299
|
+
seek(index * record_length)
|
|
298
300
|
end
|
|
299
301
|
end
|
|
300
302
|
end
|
data/lib/dbf/version.rb
CHANGED
data/spec/dbf/column_spec.rb
CHANGED
|
@@ -42,7 +42,7 @@ RSpec.describe DBF::Column do
|
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
describe '#type_cast' do
|
|
46
46
|
context 'with type N (number)' do
|
|
47
47
|
context 'when value is empty' do
|
|
48
48
|
it 'returns nil' do
|
|
@@ -52,14 +52,14 @@ RSpec.describe DBF::Column do
|
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
context '
|
|
55
|
+
context 'with 0 length' do
|
|
56
56
|
it 'returns nil' do
|
|
57
57
|
column = DBF::Column.new table, 'ColumnName', 'N', 0, 0
|
|
58
58
|
expect(column.type_cast('')).to be_nil
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
context '
|
|
62
|
+
context 'with 0 decimals' do
|
|
63
63
|
it 'casts value to Integer' do
|
|
64
64
|
value = '135'
|
|
65
65
|
column = DBF::Column.new table, 'ColumnName', 'N', 3, 0
|
|
@@ -69,11 +69,11 @@ RSpec.describe DBF::Column do
|
|
|
69
69
|
it 'supports negative Integer' do
|
|
70
70
|
value = '-135'
|
|
71
71
|
column = DBF::Column.new table, 'ColumnName', 'N', 3, 0
|
|
72
|
-
expect(column.type_cast(value)).to eq
|
|
72
|
+
expect(column.type_cast(value)).to eq(-135)
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
context '
|
|
76
|
+
context 'with more than 0 decimals' do
|
|
77
77
|
it 'casts value to Float' do
|
|
78
78
|
value = '13.5'
|
|
79
79
|
column = DBF::Column.new table, 'ColumnName', 'N', 2, 1
|
|
@@ -83,13 +83,13 @@ RSpec.describe DBF::Column do
|
|
|
83
83
|
it 'casts negative value to Float' do
|
|
84
84
|
value = '-13.5'
|
|
85
85
|
column = DBF::Column.new table, 'ColumnName', 'N', 2, 1
|
|
86
|
-
expect(column.type_cast(value)).to eq
|
|
86
|
+
expect(column.type_cast(value)).to eq(-13.5)
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
context 'with type F (float)' do
|
|
92
|
-
context '
|
|
92
|
+
context 'with 0 length' do
|
|
93
93
|
it 'returns nil' do
|
|
94
94
|
column = DBF::Column.new table, 'ColumnName', 'F', 0, 0
|
|
95
95
|
expect(column.type_cast('')).to be_nil
|
|
@@ -105,7 +105,7 @@ RSpec.describe DBF::Column do
|
|
|
105
105
|
it 'casts negative value to Float' do
|
|
106
106
|
value = '-135'
|
|
107
107
|
column = DBF::Column.new table, 'ColumnName', 'F', 3, 0
|
|
108
|
-
expect(column.type_cast(value)).to eq
|
|
108
|
+
expect(column.type_cast(value)).to eq(-135.0)
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
111
|
|
|
@@ -126,13 +126,13 @@ RSpec.describe DBF::Column do
|
|
|
126
126
|
it 'supports negative binary' do
|
|
127
127
|
column = DBF::Column.new table, 'ColumnName', 'B', 1, 2
|
|
128
128
|
expect(column.type_cast("\x00\x00\x00\x00\x00\xC0\x65\xC0")).to be_a(Float)
|
|
129
|
-
expect(column.type_cast("\x00\x00\x00\x00\x00\xC0\x65\xC0")).to eq
|
|
129
|
+
expect(column.type_cast("\x00\x00\x00\x00\x00\xC0\x65\xC0")).to eq(-174.0)
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
132
|
end
|
|
133
133
|
|
|
134
134
|
context 'with type I (integer)' do
|
|
135
|
-
context '
|
|
135
|
+
context 'with 0 length' do
|
|
136
136
|
it 'returns nil' do
|
|
137
137
|
column = DBF::Column.new table, 'ColumnName', 'I', 0, 0
|
|
138
138
|
expect(column.type_cast('')).to be_nil
|
|
@@ -142,13 +142,13 @@ RSpec.describe DBF::Column do
|
|
|
142
142
|
it 'casts value to Integer' do
|
|
143
143
|
value = "\203\171\001\000"
|
|
144
144
|
column = DBF::Column.new table, 'ColumnName', 'I', 3, 0
|
|
145
|
-
expect(column.type_cast(value)).to eq
|
|
145
|
+
expect(column.type_cast(value)).to eq 96_643
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
it 'supports negative Integer' do
|
|
149
149
|
value = "\x24\xE1\xFF\xFF"
|
|
150
150
|
column = DBF::Column.new table, 'ColumnName', 'I', 3, 0
|
|
151
|
-
expect(column.type_cast(value)).to eq
|
|
151
|
+
expect(column.type_cast(value)).to eq(-7900)
|
|
152
152
|
end
|
|
153
153
|
end
|
|
154
154
|
|
|
@@ -167,7 +167,7 @@ RSpec.describe DBF::Column do
|
|
|
167
167
|
expect(column.type_cast('n')).to be false
|
|
168
168
|
end
|
|
169
169
|
|
|
170
|
-
context '
|
|
170
|
+
context 'with 0 length' do
|
|
171
171
|
it 'returns nil' do
|
|
172
172
|
column = DBF::Column.new table, 'ColumnName', 'L', 0, 0
|
|
173
173
|
expect(column.type_cast('')).to be_nil
|
|
@@ -180,7 +180,7 @@ RSpec.describe DBF::Column do
|
|
|
180
180
|
|
|
181
181
|
context 'with valid datetime' do
|
|
182
182
|
it 'casts to DateTime' do
|
|
183
|
-
expect(column.type_cast("Nl%\000\300Z\252\003")).to eq
|
|
183
|
+
expect(column.type_cast("Nl%\000\300Z\252\003")).to eq Time.parse('2002-10-10T17:04:56+00:00')
|
|
184
184
|
end
|
|
185
185
|
end
|
|
186
186
|
|
|
@@ -190,7 +190,7 @@ RSpec.describe DBF::Column do
|
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
-
context '
|
|
193
|
+
context 'with 0 length' do
|
|
194
194
|
it 'returns nil' do
|
|
195
195
|
column = DBF::Column.new table, 'ColumnName', 'T', 0, 0
|
|
196
196
|
expect(column.type_cast('')).to be_nil
|
|
@@ -203,7 +203,7 @@ RSpec.describe DBF::Column do
|
|
|
203
203
|
|
|
204
204
|
context 'with valid date' do
|
|
205
205
|
it 'casts to Date' do
|
|
206
|
-
expect(column.type_cast('20050712')).to eq Date.new(2005,7,12)
|
|
206
|
+
expect(column.type_cast('20050712')).to eq Date.new(2005, 7, 12)
|
|
207
207
|
end
|
|
208
208
|
end
|
|
209
209
|
|
|
@@ -213,7 +213,7 @@ RSpec.describe DBF::Column do
|
|
|
213
213
|
end
|
|
214
214
|
end
|
|
215
215
|
|
|
216
|
-
context '
|
|
216
|
+
context 'with 0 length' do
|
|
217
217
|
it 'returns nil' do
|
|
218
218
|
column = DBF::Column.new table, 'ColumnName', 'D', 0, 0
|
|
219
219
|
expect(column.type_cast('')).to be_nil
|
|
@@ -232,7 +232,7 @@ RSpec.describe DBF::Column do
|
|
|
232
232
|
expect(column.type_cast(nil)).to be_nil
|
|
233
233
|
end
|
|
234
234
|
|
|
235
|
-
context '
|
|
235
|
+
context 'with 0 length' do
|
|
236
236
|
it 'returns nil' do
|
|
237
237
|
column = DBF::Column.new table, 'ColumnName', 'M', 0, 0
|
|
238
238
|
expect(column.type_cast('')).to be_nil
|
|
@@ -252,7 +252,7 @@ RSpec.describe DBF::Column do
|
|
|
252
252
|
expect(column.type_cast(nil)).to be_nil
|
|
253
253
|
end
|
|
254
254
|
|
|
255
|
-
context '
|
|
255
|
+
context 'with 0 length' do
|
|
256
256
|
it 'returns nil' do
|
|
257
257
|
column = DBF::Column.new table, 'ColumnName', 'G', 0, 0
|
|
258
258
|
expect(column.type_cast('')).to be_nil
|
|
@@ -269,14 +269,14 @@ RSpec.describe DBF::Column do
|
|
|
269
269
|
end
|
|
270
270
|
|
|
271
271
|
it 'supports negative currency' do
|
|
272
|
-
expect(column.type_cast("\xFC\xF0\xF0\xFE\xFF\xFF\xFF\xFF")).to eq
|
|
272
|
+
expect(column.type_cast("\xFC\xF0\xF0\xFE\xFF\xFF\xFF\xFF")).to eq(-1776.41)
|
|
273
273
|
end
|
|
274
274
|
|
|
275
275
|
it 'supports 64bit negative currency' do
|
|
276
|
-
expect(column.type_cast("pN'9\xFF\xFF\xFF\xFF")).to eq
|
|
276
|
+
expect(column.type_cast("pN'9\xFF\xFF\xFF\xFF")).to eq(-333_609.0)
|
|
277
277
|
end
|
|
278
278
|
|
|
279
|
-
context '
|
|
279
|
+
context 'with 0 length' do
|
|
280
280
|
it 'returns nil' do
|
|
281
281
|
column = DBF::Column.new table, 'ColumnName', 'Y', 0, 0
|
|
282
282
|
expect(column.type_cast('')).to be_nil
|
|
@@ -284,7 +284,7 @@ RSpec.describe DBF::Column do
|
|
|
284
284
|
end
|
|
285
285
|
end
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
describe '#name' do
|
|
288
288
|
it 'contains only ASCII characters' do
|
|
289
289
|
column = DBF::Column.new table, "--\x1F-\x68\x65\x6C\x6C\x6F world-\x80--", 'N', 1, 0
|
|
290
290
|
expect(column.name).to eq '---hello world---'
|
|
File without changes
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
RSpec.shared_examples_for 'DBF' do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
sum_of_column_lengths = table.columns.inject(1) { |sum, column| sum += column.length }
|
|
4
|
+
let(:header_record_length) { table.instance_eval { header.record_length } }
|
|
5
|
+
let(:sum_of_column_lengths) { table.columns.inject(1) { |sum, column| sum + column.length } }
|
|
7
6
|
|
|
7
|
+
specify 'sum of column lengths should equal record length specified in header plus one' do
|
|
8
8
|
expect(header_record_length).to eq sum_of_column_lengths
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
specify 'records should be instances of DBF::Record' do
|
|
12
|
-
table.
|
|
13
|
-
expect(record).to be_kind_of(DBF::Record)
|
|
14
|
-
end
|
|
12
|
+
expect(table).to all be_kind_of(DBF::Record)
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
specify 'record count should be the same as reported in the header' do
|
|
@@ -20,7 +18,7 @@ RSpec.shared_examples_for 'DBF' do
|
|
|
20
18
|
|
|
21
19
|
specify 'column names should not be blank' do
|
|
22
20
|
table.columns.each do |column|
|
|
23
|
-
expect(column.name).
|
|
21
|
+
expect(column.name).to_not be_empty
|
|
24
22
|
end
|
|
25
23
|
end
|
|
26
24
|
|
|
@@ -53,17 +51,17 @@ end
|
|
|
53
51
|
RSpec.describe DBF, 'of type 03 (dBase III without memo file)' do
|
|
54
52
|
let(:table) { DBF::Table.new fixture('dbase_03.dbf') }
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
it_behaves_like 'DBF'
|
|
57
55
|
|
|
58
|
-
it '
|
|
56
|
+
it 'reports the correct version number' do
|
|
59
57
|
expect(table.version).to eq '03'
|
|
60
58
|
end
|
|
61
59
|
|
|
62
|
-
it '
|
|
60
|
+
it 'reports the correct version description' do
|
|
63
61
|
expect(table.version_description).to eq 'dBase III without memo file'
|
|
64
62
|
end
|
|
65
63
|
|
|
66
|
-
it '
|
|
64
|
+
it 'determines the number of records' do
|
|
67
65
|
expect(table.record_count).to eq 14
|
|
68
66
|
end
|
|
69
67
|
end
|
|
@@ -71,17 +69,17 @@ end
|
|
|
71
69
|
RSpec.describe DBF, 'of type 30 (Visual FoxPro)' do
|
|
72
70
|
let(:table) { DBF::Table.new fixture('dbase_30.dbf') }
|
|
73
71
|
|
|
74
|
-
|
|
72
|
+
it_behaves_like 'DBF'
|
|
75
73
|
|
|
76
|
-
it '
|
|
74
|
+
it 'reports the correct version number' do
|
|
77
75
|
expect(table.version).to eq '30'
|
|
78
76
|
end
|
|
79
77
|
|
|
80
|
-
it '
|
|
78
|
+
it 'reports the correct version description' do
|
|
81
79
|
expect(table.version_description).to eq 'Visual FoxPro'
|
|
82
80
|
end
|
|
83
81
|
|
|
84
|
-
it '
|
|
82
|
+
it 'determines the number of records' do
|
|
85
83
|
expect(table.record_count).to eq 34
|
|
86
84
|
end
|
|
87
85
|
|
|
@@ -93,17 +91,17 @@ end
|
|
|
93
91
|
RSpec.describe DBF, 'of type 31 (Visual FoxPro with AutoIncrement field)' do
|
|
94
92
|
let(:table) { DBF::Table.new fixture('dbase_31.dbf') }
|
|
95
93
|
|
|
96
|
-
|
|
94
|
+
it_behaves_like 'DBF'
|
|
97
95
|
|
|
98
|
-
it '
|
|
96
|
+
it 'has a dBase version of 31' do
|
|
99
97
|
expect(table.version).to eq '31'
|
|
100
98
|
end
|
|
101
99
|
|
|
102
|
-
it '
|
|
100
|
+
it 'reports the correct version description' do
|
|
103
101
|
expect(table.version_description).to eq 'Visual FoxPro with AutoIncrement field'
|
|
104
102
|
end
|
|
105
103
|
|
|
106
|
-
it '
|
|
104
|
+
it 'determines the number of records' do
|
|
107
105
|
expect(table.record_count).to eq 77
|
|
108
106
|
end
|
|
109
107
|
end
|
|
@@ -111,17 +109,17 @@ end
|
|
|
111
109
|
RSpec.describe DBF, 'of type 83 (dBase III with memo file)' do
|
|
112
110
|
let(:table) { DBF::Table.new fixture('dbase_83.dbf') }
|
|
113
111
|
|
|
114
|
-
|
|
112
|
+
it_behaves_like 'DBF'
|
|
115
113
|
|
|
116
|
-
it '
|
|
114
|
+
it 'reports the correct version number' do
|
|
117
115
|
expect(table.version).to eq '83'
|
|
118
116
|
end
|
|
119
117
|
|
|
120
|
-
it '
|
|
118
|
+
it 'reports the correct version description' do
|
|
121
119
|
expect(table.version_description).to eq 'dBase III with memo file'
|
|
122
120
|
end
|
|
123
121
|
|
|
124
|
-
it '
|
|
122
|
+
it 'determines the number of records' do
|
|
125
123
|
expect(table.record_count).to eq 67
|
|
126
124
|
end
|
|
127
125
|
end
|
|
@@ -129,17 +127,17 @@ end
|
|
|
129
127
|
RSpec.describe DBF, 'of type 8b (dBase IV with memo file)' do
|
|
130
128
|
let(:table) { DBF::Table.new fixture('dbase_8b.dbf') }
|
|
131
129
|
|
|
132
|
-
|
|
130
|
+
it_behaves_like 'DBF'
|
|
133
131
|
|
|
134
|
-
it '
|
|
132
|
+
it 'reports the correct version number' do
|
|
135
133
|
expect(table.version).to eq '8b'
|
|
136
134
|
end
|
|
137
135
|
|
|
138
|
-
it '
|
|
136
|
+
it 'reports the correct version description' do
|
|
139
137
|
expect(table.version_description).to eq 'dBase IV with memo file'
|
|
140
138
|
end
|
|
141
139
|
|
|
142
|
-
it '
|
|
140
|
+
it 'determines the number of records' do
|
|
143
141
|
expect(table.record_count).to eq 10
|
|
144
142
|
end
|
|
145
143
|
end
|
|
@@ -147,17 +145,17 @@ end
|
|
|
147
145
|
RSpec.describe DBF, 'of type f5 (FoxPro with memo file)' do
|
|
148
146
|
let(:table) { DBF::Table.new fixture('dbase_f5.dbf') }
|
|
149
147
|
|
|
150
|
-
|
|
148
|
+
it_behaves_like 'DBF'
|
|
151
149
|
|
|
152
|
-
it '
|
|
150
|
+
it 'reports the correct version number' do
|
|
153
151
|
expect(table.version).to eq 'f5'
|
|
154
152
|
end
|
|
155
153
|
|
|
156
|
-
it '
|
|
154
|
+
it 'reports the correct version description' do
|
|
157
155
|
expect(table.version_description).to eq 'FoxPro with memo file'
|
|
158
156
|
end
|
|
159
157
|
|
|
160
|
-
it '
|
|
158
|
+
it 'determines the number of records' do
|
|
161
159
|
expect(table.record_count).to eq 975
|
|
162
160
|
end
|
|
163
161
|
|
data/spec/dbf/record_spec.rb
CHANGED
|
@@ -6,7 +6,7 @@ RSpec.describe DBF::Record do
|
|
|
6
6
|
let(:record_0) { YAML.load_file(fixture('dbase_83_record_0.yml')) }
|
|
7
7
|
let(:record_9) { YAML.load_file(fixture('dbase_83_record_9.yml')) }
|
|
8
8
|
|
|
9
|
-
it '
|
|
9
|
+
it 'returns an ordered array of attribute values' do
|
|
10
10
|
record = table.record(0)
|
|
11
11
|
expect(record.to_a).to eq record_0
|
|
12
12
|
|
|
@@ -44,13 +44,13 @@ RSpec.describe DBF::Record do
|
|
|
44
44
|
|
|
45
45
|
describe 'when other does not have attributes' do
|
|
46
46
|
it 'returns false' do
|
|
47
|
-
expect((record ==
|
|
47
|
+
expect((record == instance_double('DBF::Record'))).to be_falsey
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
describe 'if other attributes match' do
|
|
52
52
|
let(:attributes) { {x: 1, y: 2} }
|
|
53
|
-
let(:other) {
|
|
53
|
+
let(:other) { instance_double('DBF::Record', attributes: attributes) }
|
|
54
54
|
|
|
55
55
|
before do
|
|
56
56
|
allow(record).to receive(:attributes).and_return(attributes)
|
|
@@ -80,11 +80,11 @@ RSpec.describe DBF::Record do
|
|
|
80
80
|
let(:table) { DBF::Table.new fixture('cp1251.dbf') }
|
|
81
81
|
let(:record) { table.find(0) }
|
|
82
82
|
|
|
83
|
-
it '
|
|
83
|
+
it 'encodes to default system encoding' do
|
|
84
84
|
expect(record.name.encoding).to eq Encoding.default_external
|
|
85
85
|
|
|
86
86
|
# russian a
|
|
87
|
-
expect(record.name.encode('UTF-8').
|
|
87
|
+
expect(record.name.encode('UTF-8').unpack1('H4')).to eq 'd0b0'
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
@@ -92,11 +92,11 @@ RSpec.describe DBF::Record do
|
|
|
92
92
|
let(:table) { DBF::Table.new fixture('cp1251.dbf'), nil, 'cp866' }
|
|
93
93
|
let(:record) { table.find(0) }
|
|
94
94
|
|
|
95
|
-
it '
|
|
95
|
+
it 'transcodes from manually specified encoding to default system encoding' do
|
|
96
96
|
expect(record.name.encoding).to eq Encoding.default_external
|
|
97
97
|
|
|
98
98
|
# russian а encoded in cp1251 and read as if it was encoded in cp866
|
|
99
|
-
expect(record.name.encode('UTF-8').
|
|
99
|
+
expect(record.name.encode('UTF-8').unpack1('H4')).to eq 'd180'
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
end
|
data/spec/dbf/table_spec.rb
CHANGED
|
@@ -44,7 +44,7 @@ RSpec.describe DBF::Table do
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
describe '#close' do
|
|
48
48
|
before { table.close }
|
|
49
49
|
|
|
50
50
|
it 'closes the io' do
|
|
@@ -83,12 +83,12 @@ RSpec.describe DBF::Table do
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
describe '#sequel_schema' do
|
|
86
|
-
it '
|
|
86
|
+
it 'returns a valid Sequel migration by default' do
|
|
87
87
|
control_schema = File.read(fixture('dbase_83_schema_sq.txt'))
|
|
88
88
|
expect(table.sequel_schema).to eq control_schema
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
it '
|
|
91
|
+
it 'returns a limited Sequel migration when passed true' do
|
|
92
92
|
control_schema = File.read(fixture('dbase_83_schema_sq_lim.txt'))
|
|
93
93
|
expect(table.sequel_schema).to eq control_schema
|
|
94
94
|
end
|
|
@@ -129,13 +129,7 @@ RSpec.describe DBF::Table do
|
|
|
129
129
|
|
|
130
130
|
describe 'when no path param passed' do
|
|
131
131
|
it 'writes to STDOUT' do
|
|
132
|
-
|
|
133
|
-
$stdout = StringIO.new
|
|
134
|
-
table.to_csv
|
|
135
|
-
expect($stdout.string).not_to be_empty
|
|
136
|
-
ensure
|
|
137
|
-
$stdout = STDOUT
|
|
138
|
-
end
|
|
132
|
+
expect { table.to_csv }.to output.to_stdout
|
|
139
133
|
end
|
|
140
134
|
end
|
|
141
135
|
|
|
@@ -143,7 +137,7 @@ RSpec.describe DBF::Table do
|
|
|
143
137
|
before { table.to_csv('test.csv') }
|
|
144
138
|
|
|
145
139
|
it 'creates a custom csv file' do
|
|
146
|
-
expect(File.
|
|
140
|
+
expect(File).to be_exist('test.csv')
|
|
147
141
|
end
|
|
148
142
|
end
|
|
149
143
|
end
|
|
@@ -153,10 +147,18 @@ RSpec.describe DBF::Table do
|
|
|
153
147
|
allow(table).to receive(:deleted_record?).and_return(true)
|
|
154
148
|
expect(table.record(5)).to be_nil
|
|
155
149
|
end
|
|
150
|
+
|
|
151
|
+
describe 'when dbf has no column definitions' do
|
|
152
|
+
let(:dbf_path) { fixture('polygon.dbf') }
|
|
153
|
+
|
|
154
|
+
it 'raises a DBF::NoColumnsDefined error' do
|
|
155
|
+
expect { DBF::Table.new(dbf_path).record(1) }.to raise_error(DBF::NoColumnsDefined, 'The DBF file has no columns defined')
|
|
156
|
+
end
|
|
157
|
+
end
|
|
156
158
|
end
|
|
157
159
|
|
|
158
160
|
describe '#current_record' do
|
|
159
|
-
it '
|
|
161
|
+
it 'returns nil for deleted records' do
|
|
160
162
|
allow(table).to receive(:deleted_record?).and_return(true)
|
|
161
163
|
expect(table.record(0)).to be_nil
|
|
162
164
|
end
|
|
@@ -201,24 +203,24 @@ RSpec.describe DBF::Table do
|
|
|
201
203
|
expect(table.find(:all, 'WEIGHT' => 0.0)).to eq table.select { |r| r['weight'] == 0.0 }
|
|
202
204
|
end
|
|
203
205
|
|
|
204
|
-
it '
|
|
206
|
+
it 'ANDS multiple search terms' do
|
|
205
207
|
expect(table.find(:all, 'ID' => 30, :IMAGE => 'graphics/00000001/TBC01.jpg')).to be_empty
|
|
206
208
|
end
|
|
207
209
|
|
|
208
|
-
it '
|
|
209
|
-
expect(table.find(:all, 'WEIGHT' => 0.0)).
|
|
210
|
+
it 'matches original column names' do
|
|
211
|
+
expect(table.find(:all, 'WEIGHT' => 0.0)).to_not be_empty
|
|
210
212
|
end
|
|
211
213
|
|
|
212
214
|
it 'matches symbolized column names' do
|
|
213
|
-
expect(table.find(:all, :
|
|
215
|
+
expect(table.find(:all, WEIGHT: 0.0)).to_not be_empty
|
|
214
216
|
end
|
|
215
217
|
|
|
216
218
|
it 'matches downcased column names' do
|
|
217
|
-
expect(table.find(:all, 'weight' => 0.0)).
|
|
219
|
+
expect(table.find(:all, 'weight' => 0.0)).to_not be_empty
|
|
218
220
|
end
|
|
219
221
|
|
|
220
222
|
it 'matches symbolized downcased column names' do
|
|
221
|
-
expect(table.find(:all, :
|
|
223
|
+
expect(table.find(:all, weight: 0.0)).to_not be_empty
|
|
222
224
|
end
|
|
223
225
|
end
|
|
224
226
|
|
|
@@ -276,13 +278,13 @@ RSpec.describe DBF::Table do
|
|
|
276
278
|
let(:table) { DBF::Table.new fixture('dbase_03.dbf') }
|
|
277
279
|
|
|
278
280
|
it 'is false' do
|
|
279
|
-
expect(table
|
|
281
|
+
expect(table).to_not have_memo_file
|
|
280
282
|
end
|
|
281
283
|
end
|
|
282
284
|
|
|
283
285
|
describe 'with a memo file' do
|
|
284
286
|
it 'is true' do
|
|
285
|
-
expect(table
|
|
287
|
+
expect(table).to have_memo_file
|
|
286
288
|
end
|
|
287
289
|
end
|
|
288
290
|
end
|
|
@@ -293,7 +295,7 @@ RSpec.describe DBF::Table do
|
|
|
293
295
|
it 'is an array of Columns' do
|
|
294
296
|
expect(columns).to be_an(Array)
|
|
295
297
|
expect(columns).to_not be_empty
|
|
296
|
-
expect(columns.
|
|
298
|
+
expect(columns).to be_all { |c| c.is_a? DBF::Column }
|
|
297
299
|
end
|
|
298
300
|
end
|
|
299
301
|
|
|
@@ -318,7 +320,7 @@ RSpec.describe DBF::Table do
|
|
|
318
320
|
end
|
|
319
321
|
end
|
|
320
322
|
|
|
321
|
-
|
|
323
|
+
describe '#activerecord_schema_definition' do
|
|
322
324
|
context 'with type N (number)' do
|
|
323
325
|
it 'outputs an integer column' do
|
|
324
326
|
column = DBF::Column.new table, 'ColumnName', 'N', 1, 0
|
|
@@ -326,7 +328,7 @@ RSpec.describe DBF::Table do
|
|
|
326
328
|
end
|
|
327
329
|
end
|
|
328
330
|
|
|
329
|
-
|
|
331
|
+
describe 'with type B (binary)' do
|
|
330
332
|
context 'with Foxpro dbf' do
|
|
331
333
|
it 'outputs a float column' do
|
|
332
334
|
column = DBF::Column.new table, 'ColumnName', 'B', 1, 2
|
|
Binary file
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dbf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keith Morrison
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2020-08-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A small fast library for reading dBase, xBase, Clipper and FoxPro database
|
|
14
14
|
files.
|
|
@@ -48,7 +48,7 @@ files:
|
|
|
48
48
|
- lib/dbf/table.rb
|
|
49
49
|
- lib/dbf/version.rb
|
|
50
50
|
- spec/dbf/column_spec.rb
|
|
51
|
-
- spec/dbf/
|
|
51
|
+
- spec/dbf/database/foxpro_spec.rb
|
|
52
52
|
- spec/dbf/file_formats_spec.rb
|
|
53
53
|
- spec/dbf/record_spec.rb
|
|
54
54
|
- spec/dbf/table_spec.rb
|
|
@@ -90,12 +90,13 @@ files:
|
|
|
90
90
|
- spec/fixtures/foxprodb/setup.dbf
|
|
91
91
|
- spec/fixtures/foxprodb/types.CDX
|
|
92
92
|
- spec/fixtures/foxprodb/types.dbf
|
|
93
|
+
- spec/fixtures/polygon.dbf
|
|
93
94
|
- spec/spec_helper.rb
|
|
94
95
|
homepage: http://github.com/infused/dbf
|
|
95
96
|
licenses:
|
|
96
97
|
- MIT
|
|
97
98
|
metadata: {}
|
|
98
|
-
post_install_message:
|
|
99
|
+
post_install_message:
|
|
99
100
|
rdoc_options:
|
|
100
101
|
- "--charset=UTF-8"
|
|
101
102
|
require_paths:
|
|
@@ -104,21 +105,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
104
105
|
requirements:
|
|
105
106
|
- - ">="
|
|
106
107
|
- !ruby/object:Gem::Version
|
|
107
|
-
version:
|
|
108
|
+
version: 2.4.0
|
|
108
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
110
|
requirements:
|
|
110
111
|
- - ">="
|
|
111
112
|
- !ruby/object:Gem::Version
|
|
112
113
|
version: 1.3.0
|
|
113
114
|
requirements: []
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
signing_key:
|
|
115
|
+
rubygems_version: 3.1.2
|
|
116
|
+
signing_key:
|
|
117
117
|
specification_version: 4
|
|
118
118
|
summary: Read xBase files
|
|
119
119
|
test_files:
|
|
120
|
+
- spec/dbf/database/foxpro_spec.rb
|
|
120
121
|
- spec/dbf/file_formats_spec.rb
|
|
121
|
-
- spec/dbf/database_spec.rb
|
|
122
122
|
- spec/dbf/column_spec.rb
|
|
123
123
|
- spec/dbf/record_spec.rb
|
|
124
124
|
- spec/dbf/table_spec.rb
|