dbf 2.0.3 → 2.0.4
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.
- data/CHANGELOG.md +4 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +15 -17
- data/README.md +28 -24
- data/bin/dbf +3 -4
- data/dbf.gemspec +4 -4
- data/lib/dbf/column/base.rb +2 -6
- data/lib/dbf/memo/base.rb +10 -13
- data/lib/dbf/memo/dbase3.rb +2 -2
- data/lib/dbf/record.rb +19 -21
- data/lib/dbf/table.rb +27 -23
- data/lib/dbf/version.rb +1 -1
- data/spec/dbf/column_spec.rb +89 -79
- data/spec/dbf/file_formats_spec.rb +57 -57
- data/spec/dbf/record_spec.rb +32 -24
- data/spec/dbf/table_spec.rb +49 -49
- data/spec/fixtures/dbase_83_missing_memo.dbf +0 -0
- data/spec/spec_helper.rb +2 -1
- metadata +8 -7
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# 2.0.4
|
2
|
+
- memo fields return nil if memo file is missing
|
3
|
+
|
1
4
|
# 2.0.3
|
2
5
|
- set encoding if table encoding is nil
|
3
6
|
|
@@ -38,7 +41,7 @@
|
|
38
41
|
|
39
42
|
# 1.6.6
|
40
43
|
- add binary data type support to ActiveRecord schema output
|
41
|
-
|
44
|
+
|
42
45
|
# 1.6.5
|
43
46
|
- support for visual foxpro double (b) data type
|
44
47
|
|
data/Gemfile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
gemspec
|
2
|
-
source
|
2
|
+
source 'https://rubygems.org'
|
data/Gemfile.lock
CHANGED
@@ -1,30 +1,28 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dbf (
|
4
|
+
dbf (2.0.3)
|
5
|
+
fastercsv (~> 1.5.4)
|
5
6
|
|
6
7
|
GEM
|
7
|
-
remote:
|
8
|
+
remote: https://rubygems.org/
|
8
9
|
specs:
|
9
|
-
diff-lcs (1.
|
10
|
-
|
10
|
+
diff-lcs (1.2.4)
|
11
|
+
fastercsv (1.5.5)
|
11
12
|
rake (0.9.2.2)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
rspec-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
rspec-
|
20
|
-
diff-lcs (~> 1.1.2)
|
21
|
-
rspec-mocks (2.8.0)
|
13
|
+
rspec (2.13.0)
|
14
|
+
rspec-core (~> 2.13.0)
|
15
|
+
rspec-expectations (~> 2.13.0)
|
16
|
+
rspec-mocks (~> 2.13.0)
|
17
|
+
rspec-core (2.13.1)
|
18
|
+
rspec-expectations (2.13.0)
|
19
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
20
|
+
rspec-mocks (2.13.1)
|
22
21
|
|
23
22
|
PLATFORMS
|
24
23
|
ruby
|
25
24
|
|
26
25
|
DEPENDENCIES
|
27
26
|
dbf!
|
28
|
-
rake (
|
29
|
-
|
30
|
-
rspec (~> 2.8.0)
|
27
|
+
rake (>= 0.9.2)
|
28
|
+
rspec (~> 2.13.0)
|
data/README.md
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
# DBF
|
1
|
+
# DBF
|
2
|
+
[](http://travis-ci.org/infused/dbf)
|
3
|
+
[](http://badge.fury.io/rb/dbf)
|
4
|
+
[](https://codeclimate.com/github/infused/dbf)
|
5
|
+
[](https://gemnasium.com/infused/dbf)
|
2
6
|
|
3
7
|
DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro
|
4
8
|
database files
|
@@ -6,22 +10,22 @@ database files
|
|
6
10
|
* Project page: <http://github.com/infused/dbf>
|
7
11
|
* API Documentation: <http://rubydoc.info/github/infused/dbf/frames>
|
8
12
|
* Report bugs: <http://github.com/infused/dbf/issues>
|
9
|
-
* Questions: Email <mailto:keithm@infused.org> and put DBF somewhere in the
|
13
|
+
* Questions: Email <mailto:keithm@infused.org> and put DBF somewhere in the
|
10
14
|
subject line
|
11
15
|
|
12
16
|
## Compatibility
|
13
17
|
|
14
18
|
DBF is tested to work with the following versions of ruby:
|
15
19
|
|
16
|
-
* MRI Ruby 1.8.6, 1.8.7, 1.9.1, 1.9.2
|
20
|
+
* MRI Ruby 1.8.6, 1.8.7, 1.9.1, 1.9.2, 1.9.3, 2.0.0
|
17
21
|
* JRuby 1.6.x, 1.7.x
|
18
|
-
* REE 1.8.
|
19
|
-
* Rubinius (1.8
|
22
|
+
* REE 1.8.7
|
23
|
+
* Rubinius (1.8 and 1.9 modes)
|
20
24
|
|
21
25
|
## Installation
|
22
|
-
|
26
|
+
|
23
27
|
gem install dbf
|
24
|
-
|
28
|
+
|
25
29
|
## Basic Usage
|
26
30
|
|
27
31
|
Open a DBF file:
|
@@ -35,11 +39,11 @@ Enumerate all records
|
|
35
39
|
puts record.name
|
36
40
|
puts record.email
|
37
41
|
end
|
38
|
-
|
42
|
+
|
39
43
|
Find a single record
|
40
44
|
|
41
45
|
widget = widgets.find(6)
|
42
|
-
|
46
|
+
|
43
47
|
Note that find() will return nil if the requested record has been deleted
|
44
48
|
and not yet pruned from the database.
|
45
49
|
|
@@ -49,16 +53,16 @@ ways
|
|
49
53
|
widget["SlotNumber"] # original field name in dbf file
|
50
54
|
widget['slot_number'] # underscored field name string
|
51
55
|
widget[:slot_number] # underscored field name symbol
|
52
|
-
|
56
|
+
|
53
57
|
Get a hash of all attributes. The keys are the original column names.
|
54
58
|
|
55
59
|
widgets.attributes
|
56
60
|
=> {"Name" => "Thing1", "SlotNumber" => 1}
|
57
|
-
|
61
|
+
|
58
62
|
Search for records using a simple hash format. Multiple search criteria are
|
59
63
|
ANDed. Use the block form if the resulting recordset could be large, otherwise
|
60
64
|
all records will be loaded into memory.
|
61
|
-
|
65
|
+
|
62
66
|
# find all records with slot_number equal to s42
|
63
67
|
widgets.find(:all, :slot_number => 's42') do |widget|
|
64
68
|
# the record will be nil if deleted, but not yet pruned from the database
|
@@ -66,13 +70,13 @@ all records will be loaded into memory.
|
|
66
70
|
puts widget.serial_number
|
67
71
|
end
|
68
72
|
end
|
69
|
-
|
73
|
+
|
70
74
|
# find the first record with slot_number equal to s42
|
71
75
|
widgets.find :first, :slot_number => 's42'
|
72
|
-
|
76
|
+
|
73
77
|
# find record number 10
|
74
78
|
widgets.find(10)
|
75
|
-
|
79
|
+
|
76
80
|
## Migrating to ActiveRecord
|
77
81
|
|
78
82
|
An example of migrating a DBF book table to ActiveRecord using a migration:
|
@@ -80,12 +84,12 @@ An example of migrating a DBF book table to ActiveRecord using a migration:
|
|
80
84
|
require 'dbf'
|
81
85
|
|
82
86
|
class Book < ActiveRecord::Base; end
|
83
|
-
|
87
|
+
|
84
88
|
class CreateBooks < ActiveRecord::Migration
|
85
89
|
def self.up
|
86
90
|
table = DBF::Table.new('db/dbf/books.dbf')
|
87
91
|
eval(table.schema)
|
88
|
-
|
92
|
+
|
89
93
|
Book.reset_column_information
|
90
94
|
table.each do |record|
|
91
95
|
Book.create(:title => record.title, :author => record.author)
|
@@ -96,7 +100,7 @@ An example of migrating a DBF book table to ActiveRecord using a migration:
|
|
96
100
|
drop_table :books
|
97
101
|
end
|
98
102
|
end
|
99
|
-
|
103
|
+
|
100
104
|
## Command-line utility
|
101
105
|
|
102
106
|
A small command-line utility called dbf is installed along with the gem.
|
@@ -107,16 +111,16 @@ A small command-line utility called dbf is installed along with the gem.
|
|
107
111
|
-s = print summary information
|
108
112
|
-a = create an ActiveRecord::Schema
|
109
113
|
-c = create a csv file
|
110
|
-
|
114
|
+
|
111
115
|
Create an executable ActiveRecord schema:
|
112
|
-
|
116
|
+
|
113
117
|
dbf -a books.dbf > books_schema.rb
|
114
|
-
|
118
|
+
|
115
119
|
Dump all records to a CSV file:
|
116
120
|
|
117
121
|
dbf -c books.dbf > books.csv
|
118
|
-
|
119
|
-
## dBase version
|
122
|
+
|
123
|
+
## dBase version compatibility
|
120
124
|
|
121
125
|
The basic dBase data types are generally supported well. Support for the
|
122
126
|
advanced data types in dbase V and FoxPro are still experimental or not
|
@@ -135,7 +139,7 @@ for a full list of supported column types.
|
|
135
139
|
|
136
140
|
## License
|
137
141
|
|
138
|
-
Copyright (c) 2006-
|
142
|
+
Copyright (c) 2006-2013 Keith Morrison <<keithm@infused.org>>
|
139
143
|
|
140
144
|
Permission is hereby granted, free of charge, to any person
|
141
145
|
obtaining a copy of this software and associated documentation
|
data/bin/dbf
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby -s
|
2
2
|
|
3
|
-
require 'rubygems'
|
4
3
|
require 'dbf'
|
5
4
|
|
6
5
|
$a ||= false
|
@@ -23,15 +22,15 @@ else
|
|
23
22
|
table = DBF::Table.new filename
|
24
23
|
puts table.schema
|
25
24
|
end
|
26
|
-
|
25
|
+
|
27
26
|
if $s
|
28
27
|
table = DBF::Table.new filename
|
29
28
|
puts
|
30
29
|
puts "Database: #{filename}"
|
31
30
|
puts "Type: (#{table.version}) #{table.version_description}"
|
32
|
-
puts "Memo File: #{table.has_memo_file? ? 'true' : false}"
|
31
|
+
puts "Memo File: #{table.has_memo_file? ? 'true' : 'false'}"
|
33
32
|
puts "Records: #{table.record_count}"
|
34
|
-
|
33
|
+
|
35
34
|
puts "\nFields:"
|
36
35
|
puts "Name Type Length Decimal"
|
37
36
|
puts "-" * 78
|
data/dbf.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.homepage = 'http://github.com/infused/dbf'
|
11
11
|
s.summary = 'Read xBase files'
|
12
12
|
s.description = 'A small fast library for reading dBase, xBase, Clipper and FoxPro database files.'
|
13
|
-
|
13
|
+
|
14
14
|
s.executables = ['dbf']
|
15
15
|
s.rdoc_options = ['--charset=UTF-8']
|
16
16
|
s.extra_rdoc_files = ['README.md', 'CHANGELOG.md', 'MIT-LICENSE']
|
@@ -20,9 +20,9 @@ Gem::Specification.new do |s|
|
|
20
20
|
|
21
21
|
s.required_rubygems_version = '>= 1.3.0'
|
22
22
|
s.add_dependency 'fastercsv', '~> 1.5.4'
|
23
|
-
|
24
|
-
s.add_development_dependency 'rspec', '~> 2.
|
25
|
-
s.add_development_dependency 'rake', '
|
23
|
+
|
24
|
+
s.add_development_dependency 'rspec', '~> 2.13.0'
|
25
|
+
s.add_development_dependency 'rake', '>= 0.9.2'
|
26
26
|
|
27
27
|
# if RUBY_VERSION.to_f >= 1.9
|
28
28
|
# s.add_development_dependency 'ruby-debug19'
|
data/lib/dbf/column/base.rb
CHANGED
@@ -2,7 +2,7 @@ module DBF
|
|
2
2
|
module Column
|
3
3
|
class LengthError < StandardError; end
|
4
4
|
class NameError < StandardError; end
|
5
|
-
|
5
|
+
|
6
6
|
class Base
|
7
7
|
attr_reader :name, :type, :length, :decimal
|
8
8
|
|
@@ -32,7 +32,6 @@ module DBF
|
|
32
32
|
when 'D' then decode_date(value)
|
33
33
|
when 'T' then decode_datetime(value)
|
34
34
|
when 'L' then boolean(value)
|
35
|
-
when 'B' then unpack_binary(value)
|
36
35
|
when 'M' then decode_memo(value)
|
37
36
|
else encode_string(value.to_s).strip
|
38
37
|
end
|
@@ -75,7 +74,7 @@ module DBF
|
|
75
74
|
seconds = (milliseconds / 1000).to_i
|
76
75
|
DateTime.jd(days, (seconds/3600).to_i, (seconds/60).to_i % 60, seconds % 60) rescue nil
|
77
76
|
end
|
78
|
-
|
77
|
+
|
79
78
|
def decode_memo(value) #nodoc
|
80
79
|
encode_string(value) if value
|
81
80
|
end
|
@@ -88,9 +87,6 @@ module DBF
|
|
88
87
|
value.unpack('V')[0]
|
89
88
|
end
|
90
89
|
|
91
|
-
def unpack_binary(value) #nodoc
|
92
|
-
end
|
93
|
-
|
94
90
|
def boolean(value) #nodoc
|
95
91
|
value.strip =~ /^(y|t)$/i ? true : false
|
96
92
|
end
|
data/lib/dbf/memo/base.rb
CHANGED
@@ -2,33 +2,34 @@ module DBF
|
|
2
2
|
module Memo
|
3
3
|
class Base
|
4
4
|
BLOCK_HEADER_SIZE = 8
|
5
|
-
|
5
|
+
BLOCK_SIZE = 512
|
6
|
+
|
6
7
|
def self.open(filename, version)
|
7
8
|
self.new File.open(filename, 'rb'), version
|
8
9
|
end
|
9
|
-
|
10
|
+
|
10
11
|
def initialize(data, version)
|
11
12
|
@data, @version = data, version
|
12
13
|
end
|
13
|
-
|
14
|
+
|
14
15
|
def get(start_block)
|
15
16
|
if start_block > 0
|
16
|
-
build_memo start_block
|
17
|
+
build_memo start_block
|
17
18
|
end
|
18
19
|
end
|
19
|
-
|
20
|
+
|
20
21
|
def close
|
21
22
|
@data.close && @data.closed?
|
22
23
|
end
|
23
|
-
|
24
|
+
|
24
25
|
def closed?
|
25
26
|
@data.closed?
|
26
27
|
end
|
27
|
-
|
28
|
+
|
28
29
|
private
|
29
|
-
|
30
|
+
|
30
31
|
def offset(start_block) #nodoc
|
31
|
-
start_block *
|
32
|
+
start_block * BLOCK_SIZE
|
32
33
|
end
|
33
34
|
|
34
35
|
def content_size(memo_size) #nodoc
|
@@ -38,10 +39,6 @@ module DBF
|
|
38
39
|
def block_content_size #nodoc
|
39
40
|
@block_content_size ||= block_size - BLOCK_HEADER_SIZE
|
40
41
|
end
|
41
|
-
|
42
|
-
def block_size #nodoc
|
43
|
-
512
|
44
|
-
end
|
45
42
|
end
|
46
43
|
end
|
47
44
|
end
|
data/lib/dbf/memo/dbase3.rb
CHANGED
@@ -5,9 +5,9 @@ module DBF
|
|
5
5
|
@data.seek offset(start_block)
|
6
6
|
memo_string = ""
|
7
7
|
begin
|
8
|
-
block = @data.read(
|
8
|
+
block = @data.read(BLOCK_SIZE).gsub(/(\000|\032)/, '')
|
9
9
|
memo_string << block
|
10
|
-
end until block.size <
|
10
|
+
end until block.size < BLOCK_SIZE
|
11
11
|
memo_string
|
12
12
|
end
|
13
13
|
end
|
data/lib/dbf/record.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module DBF
|
2
|
-
# An instance of DBF::Record represents a row in the DBF file
|
2
|
+
# An instance of DBF::Record represents a row in the DBF file
|
3
3
|
class Record
|
4
4
|
# Initialize a new DBF::Record
|
5
|
-
#
|
5
|
+
#
|
6
6
|
# @data [String, StringIO] data
|
7
7
|
# @columns [Column]
|
8
8
|
# @version [String]
|
@@ -11,7 +11,7 @@ module DBF
|
|
11
11
|
@data = StringIO.new(data)
|
12
12
|
@columns, @version, @memo = columns, version, memo
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
# Equality
|
16
16
|
#
|
17
17
|
# @param [DBF::Record] other
|
@@ -19,14 +19,14 @@ module DBF
|
|
19
19
|
def ==(other)
|
20
20
|
other.respond_to?(:attributes) && other.attributes == attributes
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
# Maps a row to an array of values
|
24
|
-
#
|
24
|
+
#
|
25
25
|
# @return [Array]
|
26
26
|
def to_a
|
27
27
|
@columns.map {|column| attributes[column.name]}
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
# Do all search parameters match?
|
31
31
|
#
|
32
32
|
# @param [Hash] options
|
@@ -44,12 +44,12 @@ module DBF
|
|
44
44
|
attributes[@columns[index].name]
|
45
45
|
end
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
# @return [Hash]
|
49
49
|
def attributes
|
50
50
|
@attributes ||= Hash[@columns.map {|column| [column.name, init_attribute(column)]}]
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
def respond_to?(method, *args)
|
54
54
|
return true if column_names.include?(method.to_s)
|
55
55
|
super
|
@@ -68,27 +68,25 @@ module DBF
|
|
68
68
|
def column_names
|
69
69
|
@column_names ||= @columns.map {|column| column.underscored_name}
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
def init_attribute(column) #nodoc
|
73
73
|
value = if column.memo?
|
74
|
-
@memo.get
|
74
|
+
@memo && @memo.get(memo_start_block(column))
|
75
75
|
else
|
76
76
|
unpack_data(column)
|
77
77
|
end
|
78
|
-
column.type_cast
|
78
|
+
column.type_cast(value)
|
79
79
|
end
|
80
|
-
|
81
|
-
def
|
82
|
-
if %w(30 31).include?(@version)
|
83
|
-
|
84
|
-
else
|
85
|
-
unpack_data(column).to_i
|
86
|
-
end
|
80
|
+
|
81
|
+
def memo_start_block(column) #nodoc
|
82
|
+
format = 'V' if %w(30 31).include?(@version)
|
83
|
+
unpack_data(column, format).to_i
|
87
84
|
end
|
88
85
|
|
89
|
-
def unpack_data(column) #nodoc
|
90
|
-
|
86
|
+
def unpack_data(column, format=nil) #nodoc
|
87
|
+
format ||= "a#{column.length}"
|
88
|
+
@data.read(column.length).unpack(format).first
|
91
89
|
end
|
92
|
-
|
90
|
+
|
93
91
|
end
|
94
92
|
end
|