dbf 2.0.10 → 2.0.11
Sign up to get free protection for your applications and to get access to all the features.
- 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
|