dbf 3.0.0 → 3.0.1
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 +4 -0
- data/Gemfile.lock +18 -26
- data/Gemfile.travis +1 -3
- data/README.md +11 -3
- data/Rakefile +1 -1
- data/docs/supported_types.markdown +5 -5
- data/lib/dbf.rb +2 -3
- data/lib/dbf/column.rb +161 -0
- data/lib/dbf/column_type.rb +85 -0
- data/lib/dbf/database/foxpro.rb +2 -2
- data/lib/dbf/header.rb +3 -4
- data/lib/dbf/record.rb +8 -9
- data/lib/dbf/table.rb +4 -27
- data/lib/dbf/version.rb +1 -1
- data/spec/dbf/column_spec.rb +68 -91
- data/spec/dbf/database_spec.rb +2 -2
- data/spec/dbf/file_formats_spec.rb +13 -22
- data/spec/dbf/record_spec.rb +21 -26
- data/spec/dbf/table_spec.rb +30 -20
- data/spec/spec_helper.rb +15 -11
- metadata +5 -6
- data/lib/dbf/column/base.rb +0 -199
- data/lib/dbf/column/dbase.rb +0 -7
- data/lib/dbf/column/foxpro.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5eb7b6698bb3117ab55add9db5b4b3cb768c82e8
|
4
|
+
data.tar.gz: bfbc755dbd887584c102fdc249583c859c14011c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84b5da89c2953f28a559d47271614afeb452f0fbd3d770881634a4731f86279f327a67095b6191fed3e7916d886a498b880f4d69a002264b9a5fee8f62d32e09
|
7
|
+
data.tar.gz: b67fa325a58fefd66b344bba83dc58718f4ecc07bdf5676e1f0ce2a698fce1907628069fb75a75d20ca6772988c6c21f1c242949f080786f3007f18d4762549b
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,17 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dbf (
|
5
|
-
fastercsv (~> 1.5)
|
4
|
+
dbf (3.0.0)
|
6
5
|
|
7
6
|
GEM
|
8
7
|
remote: https://rubygems.org/
|
9
8
|
specs:
|
10
9
|
coderay (1.1.0)
|
11
10
|
diff-lcs (1.2.5)
|
12
|
-
fastercsv (1.5.5)
|
13
11
|
ffi (1.9.10)
|
14
|
-
ffi (1.9.10-java)
|
15
12
|
formatador (0.2.5)
|
16
13
|
guard (2.13.0)
|
17
14
|
formatador (>= 0.2.4)
|
@@ -33,42 +30,34 @@ GEM
|
|
33
30
|
lumberjack (1.0.9)
|
34
31
|
method_source (0.8.2)
|
35
32
|
nenv (0.2.0)
|
36
|
-
notiffany (0.0.
|
33
|
+
notiffany (0.0.8)
|
37
34
|
nenv (~> 0.1)
|
38
35
|
shellany (~> 0.0)
|
39
|
-
pry (0.10.
|
36
|
+
pry (0.10.2)
|
40
37
|
coderay (~> 1.1.0)
|
41
38
|
method_source (~> 0.8.1)
|
42
39
|
slop (~> 3.4)
|
43
|
-
|
44
|
-
coderay (~> 1.1.0)
|
45
|
-
method_source (~> 0.8.1)
|
46
|
-
slop (~> 3.4)
|
47
|
-
spoon (~> 0.0)
|
48
|
-
rb-fsevent (0.9.5)
|
40
|
+
rb-fsevent (0.9.6)
|
49
41
|
rb-inotify (0.9.5)
|
50
42
|
ffi (>= 0.5.0)
|
51
|
-
rspec (3.
|
52
|
-
rspec-core (~> 3.
|
53
|
-
rspec-expectations (~> 3.
|
54
|
-
rspec-mocks (~> 3.
|
55
|
-
rspec-core (3.
|
56
|
-
rspec-support (~> 3.
|
57
|
-
rspec-expectations (3.
|
43
|
+
rspec (3.4.0)
|
44
|
+
rspec-core (~> 3.4.0)
|
45
|
+
rspec-expectations (~> 3.4.0)
|
46
|
+
rspec-mocks (~> 3.4.0)
|
47
|
+
rspec-core (3.4.1)
|
48
|
+
rspec-support (~> 3.4.0)
|
49
|
+
rspec-expectations (3.4.0)
|
58
50
|
diff-lcs (>= 1.2.0, < 2.0)
|
59
|
-
rspec-support (~> 3.
|
60
|
-
rspec-mocks (3.
|
51
|
+
rspec-support (~> 3.4.0)
|
52
|
+
rspec-mocks (3.4.0)
|
61
53
|
diff-lcs (>= 1.2.0, < 2.0)
|
62
|
-
rspec-support (~> 3.
|
63
|
-
rspec-support (3.
|
54
|
+
rspec-support (~> 3.4.0)
|
55
|
+
rspec-support (3.4.0)
|
64
56
|
shellany (0.0.1)
|
65
57
|
slop (3.6.0)
|
66
|
-
spoon (0.0.4)
|
67
|
-
ffi
|
68
58
|
thor (0.19.1)
|
69
59
|
|
70
60
|
PLATFORMS
|
71
|
-
java
|
72
61
|
ruby
|
73
62
|
|
74
63
|
DEPENDENCIES
|
@@ -76,3 +65,6 @@ DEPENDENCIES
|
|
76
65
|
guard
|
77
66
|
guard-rspec
|
78
67
|
rspec
|
68
|
+
|
69
|
+
BUNDLED WITH
|
70
|
+
1.10.6
|
data/Gemfile.travis
CHANGED
data/README.md
CHANGED
@@ -28,10 +28,18 @@ DBF is tested to work with the following versions of ruby:
|
|
28
28
|
|
29
29
|
## Installation
|
30
30
|
|
31
|
+
Install the gem manually:
|
32
|
+
|
31
33
|
```
|
32
34
|
gem install dbf
|
33
35
|
```
|
34
36
|
|
37
|
+
Or add to your Gemfile:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
gem 'dbf'
|
41
|
+
```
|
42
|
+
|
35
43
|
## Basic Usage
|
36
44
|
|
37
45
|
Open a DBF file:
|
@@ -87,7 +95,7 @@ all records will be loaded into memory.
|
|
87
95
|
|
88
96
|
```ruby
|
89
97
|
# find all records with slot_number equal to s42
|
90
|
-
widgets.find(:all, :
|
98
|
+
widgets.find(:all, slot_number: 's42') do |widget|
|
91
99
|
# the record will be nil if deleted, but not yet pruned from the database
|
92
100
|
if widget
|
93
101
|
puts widget.serial_number
|
@@ -95,7 +103,7 @@ widgets.find(:all, :slot_number => 's42') do |widget|
|
|
95
103
|
end
|
96
104
|
|
97
105
|
# find the first record with slot_number equal to s42
|
98
|
-
widgets.find :first, :
|
106
|
+
widgets.find :first, slot_number: 's42'
|
99
107
|
|
100
108
|
# find record number 10
|
101
109
|
widgets.find(10)
|
@@ -149,7 +157,7 @@ class CreateBooks < ActiveRecord::Migration
|
|
149
157
|
|
150
158
|
Book.reset_column_information
|
151
159
|
table.each do |record|
|
152
|
-
Book.create(:
|
160
|
+
Book.create(title: record.title, author: record.author)
|
153
161
|
end
|
154
162
|
end
|
155
163
|
|
data/Rakefile
CHANGED
@@ -13,9 +13,9 @@
|
|
13
13
|
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
14
14
|
| 07 | Visual Objects 1.x | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
15
15
|
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
16
|
-
| 30 | Visual FoxPro | Y | Y | Y | Y | - | Y | Y |
|
16
|
+
| 30 | Visual FoxPro | Y | Y | Y | Y | - | Y | Y | Y | N | Y | N | Y | N | N | N | N | - |
|
17
17
|
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
18
|
-
| 31 | Visual FoxPro with AutoIncrement | Y | Y | Y | Y | Y | Y | Y |
|
18
|
+
| 31 | Visual FoxPro with AutoIncrement | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
|
19
19
|
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
20
20
|
| 7b | dBase IV with memo file | Y | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - |
|
21
21
|
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
@@ -27,9 +27,9 @@
|
|
27
27
|
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
28
28
|
| 8e | dBase IV with SQL table | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | N | - | - | - |
|
29
29
|
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
30
|
-
| f5 | FoxPro with memo file | Y | Y | Y | Y | Y | Y | Y |
|
30
|
+
| f5 | FoxPro with memo file | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
|
31
31
|
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
32
|
-
| fb | FoxPro without memo file | Y | Y | Y | Y | - | Y | Y |
|
32
|
+
| fb | FoxPro without memo file | Y | Y | Y | Y | - | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
|
33
33
|
+---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
34
34
|
|
35
35
|
Data type descriptions
|
@@ -50,4 +50,4 @@ Data type descriptions
|
|
50
50
|
* X = SQL compat
|
51
51
|
* @ = Timestamp
|
52
52
|
* O = Double
|
53
|
-
* + = Autoincrement
|
53
|
+
* + = Autoincrement
|
data/lib/dbf.rb
CHANGED
@@ -5,9 +5,8 @@ require 'json'
|
|
5
5
|
|
6
6
|
require 'dbf/schema'
|
7
7
|
require 'dbf/record'
|
8
|
-
require 'dbf/column
|
9
|
-
require 'dbf/
|
10
|
-
require 'dbf/column/foxpro'
|
8
|
+
require 'dbf/column'
|
9
|
+
require 'dbf/column_type'
|
11
10
|
require 'dbf/encodings'
|
12
11
|
require 'dbf/header'
|
13
12
|
require 'dbf/table'
|
data/lib/dbf/column.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
module DBF
|
2
|
+
class Column
|
3
|
+
# Raised if length is less than 1
|
4
|
+
class LengthError < StandardError; end
|
5
|
+
|
6
|
+
# Raised if name is empty
|
7
|
+
class NameError < StandardError; end
|
8
|
+
|
9
|
+
attr_reader :table, :name, :type, :length, :decimal
|
10
|
+
|
11
|
+
# Initialize a new DBF::Column
|
12
|
+
#
|
13
|
+
# @param [String] name
|
14
|
+
# @param [String] type
|
15
|
+
# @param [Fixnum] length
|
16
|
+
# @param [Fixnum] decimal
|
17
|
+
def initialize(table, name, type, length, decimal)
|
18
|
+
@table = table
|
19
|
+
@name = clean(name)
|
20
|
+
@type = type
|
21
|
+
@length = length
|
22
|
+
@decimal = decimal
|
23
|
+
@version = table.version
|
24
|
+
@encoding = table.encoding
|
25
|
+
|
26
|
+
validate_length
|
27
|
+
validate_name
|
28
|
+
end
|
29
|
+
|
30
|
+
# Cast value to native type
|
31
|
+
#
|
32
|
+
# @param [String] value
|
33
|
+
# @return [Fixnum, Float, Date, DateTime, Boolean, String]
|
34
|
+
def type_cast(value)
|
35
|
+
return nil if length == 0
|
36
|
+
|
37
|
+
klass = type_cast_class[type.to_sym]
|
38
|
+
cast_value = klass.new(value, decimal).type_cast
|
39
|
+
binary? ? cast_value : encode(cast_value)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns true if the column is a memo
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
def memo?
|
46
|
+
@memo ||= type == 'M'
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns true if the column contains binary data
|
50
|
+
#
|
51
|
+
# @return [Boolean]
|
52
|
+
def binary?
|
53
|
+
@binary ||= type == 'G'
|
54
|
+
end
|
55
|
+
|
56
|
+
# Schema definition
|
57
|
+
#
|
58
|
+
# @return [String]
|
59
|
+
def schema_definition
|
60
|
+
"\"#{underscored_name}\", #{schema_data_type}\n"
|
61
|
+
end
|
62
|
+
|
63
|
+
# Underscored name
|
64
|
+
#
|
65
|
+
# This is the column name converted to underscore format.
|
66
|
+
# For example, MyColumn will be returned as my_column.
|
67
|
+
#
|
68
|
+
# @return [String]
|
69
|
+
def underscored_name
|
70
|
+
@underscored_name ||= begin
|
71
|
+
un = name.dup
|
72
|
+
un.gsub!(/::/, '/')
|
73
|
+
un.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
74
|
+
un.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
75
|
+
un.tr!('-', '_')
|
76
|
+
un.downcase!
|
77
|
+
un
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_hash
|
82
|
+
{name: name, type: type, length: length, decimal: decimal}
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# TODO this should be a constant
|
88
|
+
def type_cast_class # nodoc
|
89
|
+
h = Hash.new(ColumnType::String)
|
90
|
+
h[:N] = ColumnType::Number
|
91
|
+
h[:I] = ColumnType::SignedLong
|
92
|
+
h[:F] = ColumnType::Float
|
93
|
+
h[:Y] = ColumnType::Currency
|
94
|
+
h[:D] = ColumnType::Date
|
95
|
+
h[:T] = ColumnType::DateTime
|
96
|
+
h[:L] = ColumnType::Boolean
|
97
|
+
h[:M] = ColumnType::Memo
|
98
|
+
h[:B] = ColumnType::Double
|
99
|
+
h[:G] = ColumnType::General
|
100
|
+
h
|
101
|
+
end
|
102
|
+
|
103
|
+
def encode(value, strip_output = false) # nodoc
|
104
|
+
return value if !value.respond_to?(:encoding)
|
105
|
+
|
106
|
+
output = @encoding ? encode_string(value) : value
|
107
|
+
strip_output ? output.strip : output
|
108
|
+
end
|
109
|
+
|
110
|
+
def encode_string(string)
|
111
|
+
string.force_encoding(@encoding).encode(*encoding_args)
|
112
|
+
end
|
113
|
+
|
114
|
+
def encoding_args # nodoc
|
115
|
+
@encoding_args ||= [
|
116
|
+
Encoding.default_external,
|
117
|
+
{undef: :replace, invalid: :replace}
|
118
|
+
]
|
119
|
+
end
|
120
|
+
|
121
|
+
def schema_data_type # nodoc
|
122
|
+
case type
|
123
|
+
when 'N', 'F'
|
124
|
+
decimal > 0 ? ':float' : ':integer'
|
125
|
+
when 'I'
|
126
|
+
':integer'
|
127
|
+
when 'Y'
|
128
|
+
':decimal, :precision => 15, :scale => 4'
|
129
|
+
when 'D'
|
130
|
+
':date'
|
131
|
+
when 'T'
|
132
|
+
':datetime'
|
133
|
+
when 'L'
|
134
|
+
':boolean'
|
135
|
+
when 'M'
|
136
|
+
':text'
|
137
|
+
when 'B'
|
138
|
+
if DBF::Table::FOXPRO_VERSIONS.keys.include?(@version)
|
139
|
+
':float'
|
140
|
+
else
|
141
|
+
':text'
|
142
|
+
end
|
143
|
+
else
|
144
|
+
":string, :limit => #{length}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def clean(value) # nodoc
|
149
|
+
truncated_value = value.strip.partition("\x00").first
|
150
|
+
truncated_value.gsub(/[^\x20-\x7E]/, '')
|
151
|
+
end
|
152
|
+
|
153
|
+
def validate_length
|
154
|
+
raise LengthError, 'field length must be 0 or greater' if length < 0
|
155
|
+
end
|
156
|
+
|
157
|
+
def validate_name
|
158
|
+
raise NameError, 'column name cannot be empty' if @name.empty?
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module DBF
|
2
|
+
module ColumnType
|
3
|
+
class Base
|
4
|
+
attr_reader :value, :decimal
|
5
|
+
|
6
|
+
def initialize(value, decimal)
|
7
|
+
@value = value
|
8
|
+
@decimal = decimal
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Number < Base
|
13
|
+
def type_cast
|
14
|
+
decimal.zero? ? value.to_i : value.to_f
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Currency < Base
|
19
|
+
def type_cast
|
20
|
+
(value.unpack('q<')[0] / 10_000.0).to_f
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class SignedLong < Base
|
25
|
+
def type_cast
|
26
|
+
value.unpack('l<')[0]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Float < Base
|
31
|
+
def type_cast
|
32
|
+
value.to_f
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Double < Base
|
37
|
+
def type_cast
|
38
|
+
value.unpack('E')[0]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Boolean < Base
|
43
|
+
def type_cast
|
44
|
+
value.strip =~ /^(y|t)$/i ? true : false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Date < Base
|
49
|
+
def type_cast
|
50
|
+
v = value.tr(' ', '0')
|
51
|
+
v !~ /\S/ ? nil : ::Date.parse(v)
|
52
|
+
rescue
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class DateTime < Base
|
58
|
+
def type_cast
|
59
|
+
days, msecs = value.unpack('l2')
|
60
|
+
secs = (msecs / 1000).to_i
|
61
|
+
::DateTime.jd(days, (secs / 3600).to_i, (secs / 60).to_i % 60, secs % 60)
|
62
|
+
rescue
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Memo < Base
|
68
|
+
def type_cast
|
69
|
+
value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class General < Base
|
74
|
+
def type_cast
|
75
|
+
value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class String < Base
|
80
|
+
def type_cast
|
81
|
+
value.strip
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|