dbf 2.0.10 → 2.0.11
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 +28 -25
- data/lib/dbf/column/base.rb +85 -59
- data/lib/dbf/column/foxpro.rb +1 -1
- data/lib/dbf/database/foxpro.rb +43 -26
- data/lib/dbf/encodings.rb +59 -59
- data/lib/dbf/header.rb +1 -1
- data/lib/dbf/memo/base.rb +7 -7
- data/lib/dbf/memo/dbase3.rb +6 -5
- data/lib/dbf/memo/dbase4.rb +2 -2
- data/lib/dbf/memo/foxpro.rb +4 -4
- data/lib/dbf/record.rb +18 -16
- data/lib/dbf/schema.rb +1 -0
- data/lib/dbf/table.rb +61 -76
- data/lib/dbf/version.rb +1 -1
- data/spec/dbf/column_spec.rb +19 -12
- data/spec/dbf/table_spec.rb +38 -37
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8aee45603d7f53ed73d7a1a6068f823330c1770a
|
4
|
+
data.tar.gz: 1478ad383f4b9b568d27ea315f16ac73690e3962
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d2bdc66cdfc536d6f0f1881dd0bf26f883521357b775ca847ffff09f1b8d7fa0f7e8a1fcc78cef4bb97067e57969f9730c9bb2de87b1c67d9e984184899f87b
|
7
|
+
data.tar.gz: 055e8f333772efc02e22556a225094cb2e1368f77cffac249c6b660fd2304c0ca5b838c86cb2994a3408eb03313bb253e686610acabe15586e23fa304a4a83e4
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,22 +1,21 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dbf (2.0.
|
4
|
+
dbf (2.0.10)
|
5
5
|
fastercsv (~> 1.5)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
celluloid (0.16.0)
|
11
|
-
timers (~> 4.0.0)
|
12
10
|
coderay (1.1.0)
|
13
11
|
diff-lcs (1.2.5)
|
14
12
|
fastercsv (1.5.5)
|
15
|
-
ffi (1.9.
|
13
|
+
ffi (1.9.10)
|
14
|
+
ffi (1.9.10-java)
|
16
15
|
formatador (0.2.5)
|
17
|
-
guard (2.
|
16
|
+
guard (2.13.0)
|
18
17
|
formatador (>= 0.2.4)
|
19
|
-
listen (
|
18
|
+
listen (>= 2.7, <= 4.0)
|
20
19
|
lumberjack (~> 1.0)
|
21
20
|
nenv (~> 0.1)
|
22
21
|
notiffany (~> 0.0)
|
@@ -24,48 +23,52 @@ GEM
|
|
24
23
|
shellany (~> 0.0)
|
25
24
|
thor (>= 0.18.1)
|
26
25
|
guard-compat (1.2.1)
|
27
|
-
guard-rspec (4.
|
26
|
+
guard-rspec (4.6.4)
|
28
27
|
guard (~> 2.1)
|
29
28
|
guard-compat (~> 1.1)
|
30
29
|
rspec (>= 2.99.0, < 4.0)
|
31
|
-
|
32
|
-
listen (2.10.0)
|
33
|
-
celluloid (~> 0.16.0)
|
30
|
+
listen (3.0.3)
|
34
31
|
rb-fsevent (>= 0.9.3)
|
35
32
|
rb-inotify (>= 0.9)
|
36
33
|
lumberjack (1.0.9)
|
37
34
|
method_source (0.8.2)
|
38
35
|
nenv (0.2.0)
|
39
|
-
notiffany (0.0.
|
36
|
+
notiffany (0.0.7)
|
40
37
|
nenv (~> 0.1)
|
41
38
|
shellany (~> 0.0)
|
42
39
|
pry (0.10.1)
|
43
40
|
coderay (~> 1.1.0)
|
44
41
|
method_source (~> 0.8.1)
|
45
42
|
slop (~> 3.4)
|
46
|
-
|
43
|
+
pry (0.10.1-java)
|
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)
|
47
49
|
rb-inotify (0.9.5)
|
48
50
|
ffi (>= 0.5.0)
|
49
|
-
rspec (3.
|
50
|
-
rspec-core (~> 3.
|
51
|
-
rspec-expectations (~> 3.
|
52
|
-
rspec-mocks (~> 3.
|
53
|
-
rspec-core (3.2
|
54
|
-
rspec-support (~> 3.
|
55
|
-
rspec-expectations (3.
|
51
|
+
rspec (3.3.0)
|
52
|
+
rspec-core (~> 3.3.0)
|
53
|
+
rspec-expectations (~> 3.3.0)
|
54
|
+
rspec-mocks (~> 3.3.0)
|
55
|
+
rspec-core (3.3.2)
|
56
|
+
rspec-support (~> 3.3.0)
|
57
|
+
rspec-expectations (3.3.1)
|
56
58
|
diff-lcs (>= 1.2.0, < 2.0)
|
57
|
-
rspec-support (~> 3.
|
58
|
-
rspec-mocks (3.2
|
59
|
+
rspec-support (~> 3.3.0)
|
60
|
+
rspec-mocks (3.3.2)
|
59
61
|
diff-lcs (>= 1.2.0, < 2.0)
|
60
|
-
rspec-support (~> 3.
|
61
|
-
rspec-support (3.
|
62
|
+
rspec-support (~> 3.3.0)
|
63
|
+
rspec-support (3.3.0)
|
62
64
|
shellany (0.0.1)
|
63
65
|
slop (3.6.0)
|
66
|
+
spoon (0.0.4)
|
67
|
+
ffi
|
64
68
|
thor (0.19.1)
|
65
|
-
timers (4.0.1)
|
66
|
-
hitimes
|
67
69
|
|
68
70
|
PLATFORMS
|
71
|
+
java
|
69
72
|
ruby
|
70
73
|
|
71
74
|
DEPENDENCIES
|
data/lib/dbf/column/base.rb
CHANGED
@@ -24,13 +24,8 @@ module DBF
|
|
24
24
|
@version = table.version
|
25
25
|
@encoding = table.encoding
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
if @name.empty?
|
32
|
-
raise NameError, 'column name cannot be empty'
|
33
|
-
end
|
27
|
+
validate_length
|
28
|
+
validate_name
|
34
29
|
end
|
35
30
|
|
36
31
|
# Cast value to native type
|
@@ -40,17 +35,8 @@ module DBF
|
|
40
35
|
def type_cast(value)
|
41
36
|
return nil if length == 0
|
42
37
|
|
43
|
-
|
44
|
-
|
45
|
-
when 'I' then unpack_unsigned_long(value)
|
46
|
-
when 'F' then value.to_f
|
47
|
-
when 'Y' then (unpack_unsigned_long(value) / 10000.0).to_f
|
48
|
-
when 'D' then decode_date(value)
|
49
|
-
when 'T' then decode_datetime(value)
|
50
|
-
when 'L' then boolean(value)
|
51
|
-
when 'M' then decode_memo(value)
|
52
|
-
else encode_string(value.to_s).strip
|
53
|
-
end
|
38
|
+
meth = type_cast_methods[type]
|
39
|
+
meth ? send(meth, value) : encode_string(value, true)
|
54
40
|
end
|
55
41
|
|
56
42
|
# Returns true if the column is a memo
|
@@ -75,95 +61,135 @@ module DBF
|
|
75
61
|
# @return [String]
|
76
62
|
def underscored_name
|
77
63
|
@underscored_name ||= begin
|
78
|
-
name.
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
64
|
+
un = name.dup
|
65
|
+
un.gsub!(/::/, '/')
|
66
|
+
un.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
67
|
+
un.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
68
|
+
un.tr!('-', '_')
|
69
|
+
un.downcase!
|
70
|
+
un
|
83
71
|
end
|
84
72
|
end
|
85
73
|
|
86
74
|
private
|
87
75
|
|
88
|
-
def
|
76
|
+
def type_cast_methods # nodoc
|
77
|
+
{
|
78
|
+
'N' => :unpack_number,
|
79
|
+
'I' => :unpack_unsigned_long,
|
80
|
+
'F' => :unpack_float,
|
81
|
+
'Y' => :unpack_currency,
|
82
|
+
'D' => :decode_date,
|
83
|
+
'T' => :decode_datetime,
|
84
|
+
'L' => :boolean,
|
85
|
+
'M' => :decode_memo,
|
86
|
+
'B' => :unpack_double
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def decode_date(value) # nodoc
|
89
91
|
value.gsub!(' ', '0')
|
90
92
|
value !~ /\S/ ? nil : Date.parse(value)
|
91
93
|
rescue
|
92
94
|
nil
|
93
95
|
end
|
94
96
|
|
95
|
-
def decode_datetime(value) #nodoc
|
97
|
+
def decode_datetime(value) # nodoc
|
96
98
|
days, msecs = value.unpack('l2')
|
97
99
|
secs = (msecs / 1000).to_i
|
98
|
-
DateTime.jd(days, (secs/3600).to_i, (secs/60).to_i % 60, secs % 60)
|
100
|
+
DateTime.jd(days, (secs / 3600).to_i, (secs / 60).to_i % 60, secs % 60)
|
99
101
|
rescue
|
100
102
|
nil
|
101
103
|
end
|
102
104
|
|
103
|
-
def decode_memo(value) #nodoc
|
105
|
+
def decode_memo(value) # nodoc
|
104
106
|
value && encode_string(value)
|
105
107
|
end
|
106
108
|
|
107
|
-
def unpack_number(value) #nodoc
|
109
|
+
def unpack_number(value) # nodoc
|
108
110
|
decimal.zero? ? value.to_i : value.to_f
|
109
111
|
end
|
110
112
|
|
111
|
-
def
|
113
|
+
def unpack_currency(value) # nodoc
|
114
|
+
(unpack_unsigned_long(value) / 10_000.0).to_f
|
115
|
+
end
|
116
|
+
|
117
|
+
def unpack_unsigned_long(value) # nodoc
|
112
118
|
value.unpack('V')[0]
|
113
119
|
end
|
114
120
|
|
115
|
-
def
|
121
|
+
def unpack_float(value) # nodoc
|
122
|
+
value.to_f
|
123
|
+
end
|
124
|
+
|
125
|
+
def unpack_double(value) # nodoc
|
126
|
+
value.unpack('E')[0]
|
127
|
+
end
|
128
|
+
|
129
|
+
def boolean(value) # nodoc
|
116
130
|
value.strip =~ /^(y|t)$/i ? true : false
|
117
131
|
end
|
118
132
|
|
119
|
-
def encode_string(value) #nodoc
|
120
|
-
|
121
|
-
if table.supports_string_encoding?
|
122
|
-
value.force_encoding(@encoding).encode(*encoding_args)
|
123
|
-
elsif table.supports_iconv?
|
124
|
-
Iconv.conv('UTF-8', @encoding, value)
|
133
|
+
def encode_string(value, strip_output = false) # nodoc
|
134
|
+
output =
|
135
|
+
if supports_encoding? && table.supports_string_encoding?
|
136
|
+
value.to_s.force_encoding(@encoding).encode(*encoding_args)
|
137
|
+
elsif supports_encoding? && table.supports_iconv?
|
138
|
+
Iconv.conv('UTF-8', @encoding, value.to_s)
|
139
|
+
else
|
140
|
+
value
|
125
141
|
end
|
126
|
-
|
127
|
-
|
128
|
-
end
|
142
|
+
|
143
|
+
strip_output ? output.strip : output
|
129
144
|
end
|
130
145
|
|
131
|
-
def encoding_args #nodoc
|
146
|
+
def encoding_args # nodoc
|
132
147
|
[Encoding.default_external, {:undef => :replace, :invalid => :replace}]
|
133
148
|
end
|
134
149
|
|
135
|
-
def schema_data_type #nodoc
|
150
|
+
def schema_data_type # nodoc
|
136
151
|
case type
|
137
|
-
when
|
138
|
-
decimal > 0 ?
|
139
|
-
when
|
140
|
-
|
141
|
-
when
|
142
|
-
|
143
|
-
when
|
144
|
-
|
145
|
-
when
|
146
|
-
|
147
|
-
when
|
148
|
-
|
149
|
-
when
|
150
|
-
|
151
|
-
when
|
152
|
+
when 'N', 'F'
|
153
|
+
decimal > 0 ? ':float' : ':integer'
|
154
|
+
when 'I'
|
155
|
+
':integer'
|
156
|
+
when 'Y'
|
157
|
+
':decimal, :precision => 15, :scale => 4'
|
158
|
+
when 'D'
|
159
|
+
':date'
|
160
|
+
when 'T'
|
161
|
+
':datetime'
|
162
|
+
when 'L'
|
163
|
+
':boolean'
|
164
|
+
when 'M'
|
165
|
+
':text'
|
166
|
+
when 'B'
|
152
167
|
if DBF::Table::FOXPRO_VERSIONS.keys.include?(@version)
|
153
|
-
|
168
|
+
':float'
|
154
169
|
else
|
155
|
-
|
170
|
+
':text'
|
156
171
|
end
|
157
172
|
else
|
158
173
|
":string, :limit => #{length}"
|
159
174
|
end
|
160
175
|
end
|
161
176
|
|
162
|
-
def clean(value) #nodoc
|
177
|
+
def clean(value) # nodoc
|
163
178
|
truncated_value = value.strip.partition("\x00").first
|
164
179
|
truncated_value.gsub(/[^\x20-\x7E]/, '')
|
165
180
|
end
|
166
181
|
|
182
|
+
def validate_length
|
183
|
+
raise LengthError, 'field length must be 0 or greater' if length < 0
|
184
|
+
end
|
185
|
+
|
186
|
+
def validate_name
|
187
|
+
raise NameError, 'column name cannot be empty' if @name.empty?
|
188
|
+
end
|
189
|
+
|
190
|
+
def supports_encoding?
|
191
|
+
@encoding && table.supports_encoding?
|
192
|
+
end
|
167
193
|
end
|
168
194
|
end
|
169
195
|
end
|
data/lib/dbf/column/foxpro.rb
CHANGED
data/lib/dbf/database/foxpro.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module DBF
|
2
|
-
# DBF::Database::Foxpro is the primary interface to a Visual Foxpro database
|
3
|
-
# When using this database container, long fieldnames
|
4
|
-
#
|
5
|
-
# Table
|
6
|
-
#
|
7
|
-
#
|
2
|
+
# DBF::Database::Foxpro is the primary interface to a Visual Foxpro database
|
3
|
+
# container (.dbc file). When using this database container, long fieldnames
|
4
|
+
# are supported, and you can reference tables directly instead of
|
5
|
+
# instantiating Table objects yourself.
|
6
|
+
# Table references are created based on the filename, but it this class
|
7
|
+
# tries to correct the table filenames because they could be wrong for
|
8
|
+
# case sensitive filesystems, e.g. when a foxpro database is uploaded to
|
9
|
+
# a linux server.
|
8
10
|
module Database
|
9
|
-
|
10
11
|
class Foxpro
|
11
12
|
# Opens a DBF::Database::Foxpro
|
12
13
|
# Examples:
|
@@ -16,15 +17,13 @@ module DBF
|
|
16
17
|
# # Calling a table
|
17
18
|
# contacts = db.contacts.record(0)
|
18
19
|
def initialize(path)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
raise DBF::FileNotFoundError.new("file not found: #{data}")
|
27
|
-
end
|
20
|
+
@path = path
|
21
|
+
@dirname = File.dirname(@path)
|
22
|
+
@db = DBF::Table.new(@path)
|
23
|
+
@tables = extract_dbc_data
|
24
|
+
|
25
|
+
rescue Errno::ENOENT
|
26
|
+
raise DBF::FileNotFoundError, "file not found: #{data}"
|
28
27
|
end
|
29
28
|
|
30
29
|
def table_names
|
@@ -49,14 +48,14 @@ module DBF
|
|
49
48
|
path = Dir.glob(glob).find { |match| match.downcase == example.downcase }
|
50
49
|
|
51
50
|
unless path && File.exist?(path)
|
52
|
-
raise DBF::FileNotFoundError
|
51
|
+
raise DBF::FileNotFoundError, "related table not found: #{name}"
|
53
52
|
end
|
54
53
|
|
55
54
|
path
|
56
55
|
end
|
57
56
|
|
58
57
|
def method_missing(method, *args) # nodoc
|
59
|
-
if
|
58
|
+
if table_names.index(method.to_s)
|
60
59
|
table method.to_s
|
61
60
|
else
|
62
61
|
super
|
@@ -65,9 +64,10 @@ module DBF
|
|
65
64
|
|
66
65
|
private
|
67
66
|
|
68
|
-
# This method extracts the data from the database container. This is
|
69
|
-
# with a treelike structure. Field definitions
|
70
|
-
# but only the long name
|
67
|
+
# This method extracts the data from the database container. This is
|
68
|
+
# just an ordinary table with a treelike structure. Field definitions
|
69
|
+
# are in the same order as in the linked tables but only the long name
|
70
|
+
# is provided.
|
71
71
|
def extract_dbc_data # nodoc
|
72
72
|
data = {}
|
73
73
|
@db.each do |record|
|
@@ -76,18 +76,36 @@ module DBF
|
|
76
76
|
case record.objecttype
|
77
77
|
when 'Table'
|
78
78
|
# This is a related table
|
79
|
-
|
79
|
+
process_table record, data
|
80
80
|
when 'Field'
|
81
81
|
# This is a related field. The parentid points to the table object.
|
82
82
|
# Create using the parentid if the parentid is still unknown.
|
83
|
-
|
84
|
-
data[record.parentid][:fields] << record.objectname
|
83
|
+
process_field record, data
|
85
84
|
end
|
86
85
|
end
|
87
86
|
|
88
|
-
Hash[
|
87
|
+
Hash[
|
88
|
+
data.values.map { |v| [v[:name], v[:fields]] }
|
89
|
+
]
|
90
|
+
end
|
91
|
+
|
92
|
+
def process_table(record, data)
|
93
|
+
id = record.objectid
|
94
|
+
name = record.objectname
|
95
|
+
data[id] = table_field_hash(name)
|
89
96
|
end
|
90
97
|
|
98
|
+
def process_field(record, data)
|
99
|
+
id = record.parentid
|
100
|
+
name = 'UNKNOWN'
|
101
|
+
field = record.objectname
|
102
|
+
data[id] ||= table_field_hash(name)
|
103
|
+
data[id][:fields] << field
|
104
|
+
end
|
105
|
+
|
106
|
+
def table_field_hash(name)
|
107
|
+
{:name => name, :fields => []}
|
108
|
+
end
|
91
109
|
end
|
92
110
|
|
93
111
|
class Table < DBF::Table
|
@@ -102,7 +120,6 @@ module DBF
|
|
102
120
|
long_name = long_names[columns.index(column)]
|
103
121
|
column_class.new(self, long_name, column.type, column.length, column.decimal)
|
104
122
|
end
|
105
|
-
|
106
123
|
end
|
107
124
|
end
|
108
125
|
end
|