dbf 0.4.6 → 0.4.7
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +22 -0
- data/README.txt +67 -0
- data/Rakefile +9 -2
- data/lib/dbf.rb +3 -1
- data/lib/dbf/field.rb +12 -0
- data/lib/dbf/globals.rb +22 -0
- data/lib/dbf/reader.rb +2 -84
- data/lib/dbf/record.rb +66 -0
- data/test/dbase_iii_read_test.rb +1 -1
- data/test/foxpro_read_test.rb +1 -1
- metadata +12 -7
- data/README +0 -37
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.txt
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
= DBF
|
2
|
+
|
3
|
+
DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro database files. It is written completely in Ruby and has no external dependencies.
|
4
|
+
|
5
|
+
== Features
|
6
|
+
|
7
|
+
* No external dependencies
|
8
|
+
* DB fields are type cast
|
9
|
+
* Date/Time fields are returned as either a Time or Date object. Date
|
10
|
+
will only be used if the date is outside the range for Time.
|
11
|
+
|
12
|
+
== Installation
|
13
|
+
|
14
|
+
gem install dbf
|
15
|
+
|
16
|
+
== Usage
|
17
|
+
|
18
|
+
reader = DBF::Reader.new("old_data.dbf")
|
19
|
+
|
20
|
+
reader.records.each do |record|
|
21
|
+
puts record['name']
|
22
|
+
puts record['email']
|
23
|
+
end
|
24
|
+
|
25
|
+
puts reader.records[4]['name']
|
26
|
+
puts reader.record(4)['name']
|
27
|
+
|
28
|
+
=== A note on record vs. records
|
29
|
+
|
30
|
+
DBF::Reader#records is an in-memory array of all rows in the database. All
|
31
|
+
rows are loaded the first time that the method is called. Subsequent calls
|
32
|
+
retrieve the row from memory.
|
33
|
+
|
34
|
+
DBF::Reader#record retrieves the requested row from the database each time
|
35
|
+
it is called.
|
36
|
+
|
37
|
+
Using records is probably faster most of the time. Record is more appropriate
|
38
|
+
for very large databases where you don't want the whole db loaded into memory.
|
39
|
+
|
40
|
+
== Limitations and known bugs
|
41
|
+
|
42
|
+
* DBF is read-only. Writing to the database has not yet been implemented.
|
43
|
+
|
44
|
+
== License
|
45
|
+
|
46
|
+
Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org>
|
47
|
+
|
48
|
+
Permission is hereby granted, free of charge, to any person
|
49
|
+
obtaining a copy of this software and associated documentation
|
50
|
+
files (the "Software"), to deal in the Software without
|
51
|
+
restriction, including without limitation the rights to use,
|
52
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
53
|
+
copies of the Software, and to permit persons to whom the
|
54
|
+
Software is furnished to do so, subject to the following
|
55
|
+
conditions:
|
56
|
+
|
57
|
+
The above copyright notice and this permission notice shall be
|
58
|
+
included in all copies or substantial portions of the Software.
|
59
|
+
|
60
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
61
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
62
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
63
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
64
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
65
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
66
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
67
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
require 'hoe'
|
2
|
+
require 'spec/rake/spectask'
|
2
3
|
|
3
4
|
PKG_NAME = "dbf"
|
4
|
-
PKG_VERSION = "0.4.
|
5
|
+
PKG_VERSION = "0.4.7"
|
5
6
|
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
6
7
|
|
7
8
|
Hoe.new PKG_NAME, PKG_VERSION do |p|
|
8
9
|
p.rubyforge_name = PKG_NAME
|
9
10
|
p.author = "Keith Morrison"
|
10
11
|
p.email = "keithm@infused.org"
|
11
|
-
p.summary = "A library for reading dBase
|
12
|
+
p.summary = "A small fast library for reading dBase, xBase, Clipper and FoxPro database files."
|
12
13
|
p.url = "http://dbf.rubyforge.org"
|
13
14
|
p.need_tar = true
|
14
15
|
p.need_zip = true
|
@@ -23,3 +24,9 @@ Rake::TestTask.new :test do |t|
|
|
23
24
|
t.pattern = 'test/*_test.rb'
|
24
25
|
t.verbose = true
|
25
26
|
end
|
27
|
+
|
28
|
+
desc "Run specs"
|
29
|
+
Spec::Rake::SpecTask.new :spec do |t|
|
30
|
+
t.spec_opts = ["-f specdoc"]
|
31
|
+
t.spec_files = FileList['spec/**/*spec.rb']
|
32
|
+
end
|
data/lib/dbf.rb
CHANGED
data/lib/dbf/field.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module DBF
|
2
|
+
class FieldLengthError < DBFError; end
|
3
|
+
class Field
|
4
|
+
attr_reader :name, :type, :length, :decimal
|
5
|
+
|
6
|
+
def initialize(name, type, length, decimal)
|
7
|
+
raise FieldLengthError, "field length must be greater than 0" unless length > 0
|
8
|
+
@name, @type, @length, @decimal = name.gsub(/\0/, ''), type, length, decimal
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
data/lib/dbf/globals.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module DBF
|
2
|
+
DBF_HEADER_SIZE = 32
|
3
|
+
FPT_HEADER_SIZE = 512
|
4
|
+
FPT_BLOCK_HEADER_SIZE = 8
|
5
|
+
DATE_REGEXP = /([\d]{4})([\d]{2})([\d]{2})/
|
6
|
+
VERSION_DESCRIPTIONS = {
|
7
|
+
"02" => "FoxBase",
|
8
|
+
"03" => "dBase III without memo file",
|
9
|
+
"04" => "dBase IV without memo file",
|
10
|
+
"05" => "dBase V without memo file",
|
11
|
+
"30" => "Visual FoxPro",
|
12
|
+
"31" => "Visual FoxPro with AutoIncrement field",
|
13
|
+
"7b" => "dBase IV with memo file",
|
14
|
+
"83" => "dBase III with memo file",
|
15
|
+
"8b" => "dBase IV with memo file",
|
16
|
+
"8e" => "dBase IV with SQL table",
|
17
|
+
"f5" => "FoxPro with memo file",
|
18
|
+
"fb" => "FoxPro without memo file"
|
19
|
+
}
|
20
|
+
|
21
|
+
class DBFError < StandardError; end
|
22
|
+
end
|
data/lib/dbf/reader.rb
CHANGED
@@ -44,7 +44,7 @@ module DBF
|
|
44
44
|
seek_to_record(0)
|
45
45
|
@records ||= Array.new(@record_count) do |i|
|
46
46
|
if active_record?
|
47
|
-
Record.new(self, @data_file, @memo_file)
|
47
|
+
DBF::Record.new(self, @data_file, @memo_file)
|
48
48
|
else
|
49
49
|
seek_to_record(i + 1)
|
50
50
|
nil
|
@@ -117,86 +117,4 @@ module DBF
|
|
117
117
|
|
118
118
|
end
|
119
119
|
|
120
|
-
|
121
|
-
|
122
|
-
class Field
|
123
|
-
attr_accessor :name, :type, :length, :decimal
|
124
|
-
|
125
|
-
def initialize(name, type, length, decimal)
|
126
|
-
raise FieldError, "field length must be greater than 0" unless length > 0
|
127
|
-
self.name, self.type, self.length, self.decimal = name.strip, type, length, decimal
|
128
|
-
end
|
129
|
-
|
130
|
-
def name=(name)
|
131
|
-
@name = name.gsub(/\0/, '')
|
132
|
-
end
|
133
|
-
|
134
|
-
end
|
135
|
-
|
136
|
-
class Record < Hash
|
137
|
-
|
138
|
-
def initialize(reader, data_file, memo_file)
|
139
|
-
@reader, @data_file, @memo_file = reader, data_file, memo_file
|
140
|
-
reader.fields.each do |field|
|
141
|
-
case field.type
|
142
|
-
when 'N' # number
|
143
|
-
self[field.name] = field.decimal == 0 ? unpack_string(field).to_i : unpack_string(field).to_f
|
144
|
-
when 'D' # date
|
145
|
-
raw = unpack_string(field).strip
|
146
|
-
unless raw.empty?
|
147
|
-
begin
|
148
|
-
self[field.name] = Time.gm(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i})
|
149
|
-
rescue
|
150
|
-
self[field.name] = Date.new(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i})
|
151
|
-
end
|
152
|
-
end
|
153
|
-
when 'M' # memo
|
154
|
-
starting_block = unpack_string(field).to_i
|
155
|
-
self[field.name] = read_memo(starting_block)
|
156
|
-
when 'L' # logical
|
157
|
-
self[field.name] = unpack_string(field) =~ /^(y|t)$/i ? true : false
|
158
|
-
else
|
159
|
-
self[field.name] = unpack_string(field)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
self
|
163
|
-
end
|
164
|
-
|
165
|
-
def unpack_field(field)
|
166
|
-
@data_file.read(field.length).unpack("a#{field.length}")
|
167
|
-
end
|
168
|
-
|
169
|
-
def unpack_string(field)
|
170
|
-
unpack_field(field).to_s
|
171
|
-
end
|
172
|
-
|
173
|
-
def read_memo(start_block)
|
174
|
-
return nil if start_block == 0
|
175
|
-
@memo_file.seek(start_block * @reader.memo_block_size)
|
176
|
-
if @reader.memo_file_format == :fpt
|
177
|
-
memo_type, memo_size, memo_string = @memo_file.read(@reader.memo_block_size).unpack("NNa56")
|
178
|
-
|
179
|
-
memo_block_content_size = @reader.memo_block_size - FPT_BLOCK_HEADER_SIZE
|
180
|
-
if memo_size > memo_block_content_size
|
181
|
-
memo_string << @memo_file.read(memo_size - @reader.memo_block_size + FPT_BLOCK_HEADER_SIZE)
|
182
|
-
elsif memo_size > 0 and memo_size < memo_block_content_size
|
183
|
-
memo_string = memo_string[0, memo_size]
|
184
|
-
end
|
185
|
-
else
|
186
|
-
case @reader.version
|
187
|
-
when "83" # dbase iii
|
188
|
-
memo_string = ""
|
189
|
-
loop do
|
190
|
-
memo_string << block = @memo_file.read(512)
|
191
|
-
break if block.strip.size < 512
|
192
|
-
end
|
193
|
-
when "8b" # dbase iv
|
194
|
-
memo_type, memo_size = @memo_file.read(8).unpack("LL")
|
195
|
-
memo_string = @memo_file.read(memo_size)
|
196
|
-
end
|
197
|
-
end
|
198
|
-
memo_string
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
end
|
120
|
+
end
|
data/lib/dbf/record.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module DBF
|
2
|
+
class Record < Hash
|
3
|
+
def initialize(reader, data_file, memo_file)
|
4
|
+
@reader, @data_file, @memo_file = reader, data_file, memo_file
|
5
|
+
reader.fields.each do |field|
|
6
|
+
case field.type
|
7
|
+
when 'N' # number
|
8
|
+
self[field.name] = field.decimal == 0 ? unpack_string(field).to_i : unpack_string(field).to_f
|
9
|
+
when 'D' # date
|
10
|
+
raw = unpack_string(field).strip
|
11
|
+
unless raw.empty?
|
12
|
+
begin
|
13
|
+
self[field.name] = Time.gm(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i})
|
14
|
+
rescue
|
15
|
+
self[field.name] = Date.new(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
when 'M' # memo
|
19
|
+
starting_block = unpack_string(field).to_i
|
20
|
+
self[field.name] = read_memo(starting_block)
|
21
|
+
when 'L' # logical
|
22
|
+
self[field.name] = unpack_string(field) =~ /^(y|t)$/i ? true : false
|
23
|
+
else
|
24
|
+
self[field.name] = unpack_string(field)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def unpack_field(field)
|
31
|
+
@data_file.read(field.length).unpack("a#{field.length}")
|
32
|
+
end
|
33
|
+
|
34
|
+
def unpack_string(field)
|
35
|
+
unpack_field(field).to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def read_memo(start_block)
|
39
|
+
return nil if start_block == 0
|
40
|
+
@memo_file.seek(start_block * @reader.memo_block_size)
|
41
|
+
if @reader.memo_file_format == :fpt
|
42
|
+
memo_type, memo_size, memo_string = @memo_file.read(@reader.memo_block_size).unpack("NNa56")
|
43
|
+
|
44
|
+
memo_block_content_size = @reader.memo_block_size - FPT_BLOCK_HEADER_SIZE
|
45
|
+
if memo_size > memo_block_content_size
|
46
|
+
memo_string << @memo_file.read(memo_size - @reader.memo_block_size + FPT_BLOCK_HEADER_SIZE)
|
47
|
+
elsif memo_size > 0 and memo_size < memo_block_content_size
|
48
|
+
memo_string = memo_string[0, memo_size]
|
49
|
+
end
|
50
|
+
else
|
51
|
+
case @reader.version
|
52
|
+
when "83" # dbase iii
|
53
|
+
memo_string = ""
|
54
|
+
loop do
|
55
|
+
memo_string << block = @memo_file.read(512)
|
56
|
+
break if block.strip.size < 512
|
57
|
+
end
|
58
|
+
when "8b" # dbase iv
|
59
|
+
memo_type, memo_size = @memo_file.read(8).unpack("LL")
|
60
|
+
memo_string = @memo_file.read(memo_size)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
memo_string
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/test/dbase_iii_read_test.rb
CHANGED
@@ -21,7 +21,7 @@ class DBaseIIIReadTest < Test::Unit::TestCase
|
|
21
21
|
:testable_logical_field_names => [],
|
22
22
|
:testable_memo_field_names => []
|
23
23
|
}
|
24
|
-
@dbf = DBF::Reader.new
|
24
|
+
@dbf = DBF::Reader.new "#{File.dirname(__FILE__)}/databases/dbase_iii.dbf"
|
25
25
|
end
|
26
26
|
|
27
27
|
end
|
data/test/foxpro_read_test.rb
CHANGED
@@ -21,7 +21,7 @@ class FoxproReadTest < Test::Unit::TestCase
|
|
21
21
|
:testable_logical_field_names => [],
|
22
22
|
:testable_memo_field_names => ["OBSE"]
|
23
23
|
}
|
24
|
-
@dbf = DBF::Reader.new
|
24
|
+
@dbf = DBF::Reader.new "#{File.dirname(__FILE__)}/databases/foxpro.dbf"
|
25
25
|
end
|
26
26
|
|
27
27
|
# make sure we're grabbing the correct memo
|
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.4
|
3
3
|
specification_version: 1
|
4
4
|
name: dbf
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.4.
|
7
|
-
date: 2007-05-
|
8
|
-
summary: A library for reading dBase
|
6
|
+
version: 0.4.7
|
7
|
+
date: 2007-05-25 00:00:00 -07:00
|
8
|
+
summary: A small fast library for reading dBase, xBase, Clipper and FoxPro database files.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
11
|
email: keithm@infused.org
|
@@ -30,9 +30,13 @@ authors:
|
|
30
30
|
- Keith Morrison
|
31
31
|
files:
|
32
32
|
- Rakefile
|
33
|
-
- README
|
33
|
+
- README.txt
|
34
|
+
- LICENSE.txt
|
34
35
|
- lib/dbf.rb
|
36
|
+
- lib/dbf/field.rb
|
37
|
+
- lib/dbf/globals.rb
|
35
38
|
- lib/dbf/reader.rb
|
39
|
+
- lib/dbf/record.rb
|
36
40
|
- test/common.rb
|
37
41
|
- test/dbase_iii_read_test.rb
|
38
42
|
- test/foxpro_read_test.rb
|
@@ -47,8 +51,9 @@ test_files: []
|
|
47
51
|
rdoc_options:
|
48
52
|
- --main
|
49
53
|
- README.txt
|
50
|
-
extra_rdoc_files:
|
51
|
-
|
54
|
+
extra_rdoc_files:
|
55
|
+
- README.txt
|
56
|
+
- LICENSE.txt
|
52
57
|
executables: []
|
53
58
|
|
54
59
|
extensions: []
|
data/README
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
= DBF
|
2
|
-
A dBase I/O library.
|
3
|
-
|
4
|
-
== Features
|
5
|
-
|
6
|
-
* No external dependencies
|
7
|
-
* DB fields are type cast
|
8
|
-
* Date/Time fields are returned as either a Time or Date object. Date
|
9
|
-
will only be used if the date is outside the range for Time.
|
10
|
-
|
11
|
-
== Limitations
|
12
|
-
|
13
|
-
* Writing to the db has not been implemented yet
|
14
|
-
|
15
|
-
== Usage
|
16
|
-
|
17
|
-
reader = DBF::Reader.new("old_data.dbf")
|
18
|
-
|
19
|
-
reader.records.each do |record|
|
20
|
-
puts record['name']
|
21
|
-
puts record['email']
|
22
|
-
end
|
23
|
-
|
24
|
-
puts reader.records[4]['name']
|
25
|
-
puts reader.record(4)['name']
|
26
|
-
|
27
|
-
=== A note on record vs. records
|
28
|
-
|
29
|
-
DBF::Reader#records is an in-memory array of all rows in the database. All
|
30
|
-
rows are loaded the first time that the method is called. Subsequent calls
|
31
|
-
retrieve the row from memory.
|
32
|
-
|
33
|
-
DBF::Reader#record retrieves the requested row from the database each time
|
34
|
-
it is called.
|
35
|
-
|
36
|
-
Using records is probably faster most of the time. Record is more appropriate
|
37
|
-
for very large databases where you don't want the whole db loaded into memory.
|