dbf 0.4.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,9 @@
1
+ == 0.5.0 / 2007-05-25
2
+
3
+ * New find method
4
+ * Full compatibility with the two flavors of memo file
5
+ * Two modes of operation:
6
+ * In memory (default): All records are loaded into memory on the first
7
+ request. Records are retrieved from memory for all subsequent requests.
8
+ * File I/O: All records are retrieved from disk on every request
9
+ * Improved documentation and more usage examples
data/Manifest.txt ADDED
@@ -0,0 +1,28 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ benchmarks/performance.rb
6
+ benchmarks/seek_benchmark.rb
7
+ lib/dbf.rb
8
+ lib/dbf/field.rb
9
+ lib/dbf/globals.rb
10
+ lib/dbf/reader.rb
11
+ lib/dbf/record.rb
12
+ spec/field_spec.rb
13
+ spec/fixtures/dbase_iii_memo_schema.rb
14
+ spec/reader_spec.rb
15
+ spec/record_spec.rb
16
+ spec/spec_helper.rb
17
+ test/common.rb
18
+ test/databases/dbase_iii.dbf
19
+ test/databases/dbase_iii_memo.dbf
20
+ test/databases/dbase_iii_memo.dbt
21
+ test/databases/foxpro.dbf
22
+ test/databases/foxpro.fpt
23
+ test/databases/visual_foxpro.dbf
24
+ test/databases/visual_foxpro.fpt
25
+ test/dbase_iii_memo_read_test.rb
26
+ test/dbase_iii_read_test.rb
27
+ test/foxpro_read_test.rb
28
+ test/visual_foxpro_read_test.rb
data/README.txt CHANGED
@@ -2,47 +2,59 @@
2
2
 
3
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
4
 
5
+ Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org, www.infused.org>
6
+
7
+ * Official project page: http://rubyforge.org/projects/dbf
8
+ * API Documentation: http://dbf.rubyforge.org/docs
9
+ * To report bugs: http://www.rubyforge.org/tracker/?group_id=2009
10
+ * Questions: Email keithm@infused.org and put DBF somewhere in the subject
11
+ line
12
+
5
13
  == Features
6
14
 
7
15
  * No external dependencies
8
- * DB fields are type cast
16
+ * Fields are type cast to the appropriate Ruby types
9
17
  * 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.
18
+ will only be used if the date is out of range for Ruby's built in Time
19
+ class.
20
+ * Ability to dump the database schema in the portable ActiveRecord::Schema
21
+ format.
11
22
 
12
23
  == Installation
13
-
24
+
14
25
  gem install dbf
15
26
 
16
- == Usage
27
+ == Basic Usage
28
+
29
+ require 'rubygems'
30
+ require 'dbf'
17
31
 
18
32
  reader = DBF::Reader.new("old_data.dbf")
19
33
 
34
+ # Print the 'name' field from record number 4
35
+ puts reader.record(4)['name']
36
+
37
+ # Print the 'name' and 'address' fields from each record
20
38
  reader.records.each do |record|
21
39
  puts record['name']
22
40
  puts record['email']
23
41
  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
42
 
43
+ # Find records
44
+ reader.find :all, :first_name => 'Keith'
45
+ reader.find :all, :first_name => 'Keith', :last_name => 'Morrison'
46
+ reader.find :first, :first_name => 'Keith'
47
+ reader.find(10)
48
+
40
49
  == Limitations and known bugs
41
50
 
42
- * DBF is read-only. Writing to the database has not yet been implemented.
51
+ * DBF is read-only at the moment
52
+ * Index files are not utilized
43
53
 
44
54
  == License
45
55
 
56
+ (The MIT Licence)
57
+
46
58
  Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org>
47
59
 
48
60
  Permission is hereby granted, free of charge, to any person
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'hoe'
2
2
  require 'spec/rake/spectask'
3
3
 
4
4
  PKG_NAME = "dbf"
5
- PKG_VERSION = "0.4.7"
5
+ PKG_VERSION = "0.5.0"
6
6
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
7
7
 
8
8
  Hoe.new PKG_NAME, PKG_VERSION do |p|
@@ -10,6 +10,7 @@ Hoe.new PKG_NAME, PKG_VERSION do |p|
10
10
  p.author = "Keith Morrison"
11
11
  p.email = "keithm@infused.org"
12
12
  p.summary = "A small fast library for reading dBase, xBase, Clipper and FoxPro database files."
13
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
13
14
  p.url = "http://dbf.rubyforge.org"
14
15
  p.need_tar = true
15
16
  p.need_zip = true
File without changes
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ require 'benchmark'
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
4
+ require 'dbf'
5
+
6
+ puts
7
+ puts "Runs 5000 random row seeks first using the I/O based record(n) method and then using"
8
+ puts "using the array of records."
9
+ puts
10
+
11
+ iterations = 5000
12
+ Benchmark.bm(20) do |x|
13
+ @dbf = DBF::Reader.new(File.join(File.dirname(__FILE__), '..', 'test', 'databases', 'foxpro.dbf'))
14
+ max = @dbf.record_count + 1
15
+
16
+ x.report("I/O based record(n)") { iterations.times { @dbf.record(rand(max)) } }
17
+ x.report("array of records[n]") { iterations.times { @dbf.records[rand(max)] } }
18
+ end
data/lib/dbf.rb CHANGED
@@ -3,4 +3,4 @@ require 'date'
3
3
  require 'dbf/globals'
4
4
  require 'dbf/record'
5
5
  require 'dbf/field'
6
- require 'dbf/reader'
6
+ require 'dbf/reader'
data/lib/dbf/reader.rb CHANGED
@@ -1,119 +1,247 @@
1
1
  module DBF
2
2
  class Reader
3
+ # The total number of fields (columns)
3
4
  attr_reader :field_count
5
+ # An array of DBF::Field records
4
6
  attr_reader :fields
7
+ # The total number of records. This number includes any deleted records.
5
8
  attr_reader :record_count
9
+ # Internal dBase version number
6
10
  attr_reader :version
11
+ # Last updated datetime
7
12
  attr_reader :last_updated
13
+ # Either :fpt or :dpt
8
14
  attr_reader :memo_file_format
15
+ # The block size for memo records
9
16
  attr_reader :memo_block_size
10
17
 
11
- def initialize(file)
12
- @data_file = File.open(file, 'rb')
13
- @memo_file = open_memo(file)
18
+ # Initialize a new DBF::Reader.
19
+ # Example:
20
+ # reader = DBF::Reader.new 'data.dbf'
21
+ def initialize(filename)
22
+ @in_memory = true
23
+ @data_file = File.open(filename, 'rb')
24
+ @memo_file = open_memo(filename)
14
25
  reload!
15
26
  end
16
27
 
28
+ # Reloads the database and memo files
17
29
  def reload!
30
+ @records = nil
18
31
  get_header_info
19
32
  get_memo_header_info if @memo_file
20
33
  get_field_descriptors
21
34
  end
22
35
 
36
+ # Returns true if there is a corresponding memo file
23
37
  def has_memo_file?
24
38
  @memo_file ? true : false
25
39
  end
26
40
 
27
- def open_memo(file)
28
- %w(fpt FPT dbt DBT).each do |extension|
29
- filename = file.sub(/#{File.extname(file)[1..-1]}$/, extension)
30
- if File.exists?(filename)
31
- @memo_file_format = extension.downcase.to_sym
32
- return File.open(filename, 'rb')
33
- end
34
- end
35
- nil
41
+ # If true, DBF::Reader will load all records into memory. If false, records are retrieved using file I/O.
42
+ def in_memory?
43
+ @in_memory
36
44
  end
37
45
 
46
+ # Tells DBF::Reader whether to load all records into memory. Defaults to true.
47
+ # You may need to set this to false if the database is very large in order to reduce memory usage.
48
+ def in_memory=(boolean)
49
+ @in_memory = boolean
50
+ end
51
+
52
+ # Returns an instance of DBF::Field for <b>field_name</b>. <b>field_name</b>
53
+ # can be a symbol or a string.
38
54
  def field(field_name)
39
55
  @fields.detect {|f| f.name == field_name.to_s}
40
56
  end
41
57
 
42
58
  # An array of all the records contained in the database file
43
59
  def records
44
- seek_to_record(0)
45
- @records ||= Array.new(@record_count) do |i|
46
- if active_record?
47
- DBF::Record.new(self, @data_file, @memo_file)
48
- else
49
- seek_to_record(i + 1)
50
- nil
51
- end
60
+ if in_memory?
61
+ @records ||= get_all_records_from_file
62
+ else
63
+ get_all_records_from_file
52
64
  end
53
65
  end
54
66
 
55
67
  alias_method :rows, :records
56
68
 
57
- # Returns the record at <a>index</i> by seeking to the record in the
58
- # physical database file. See the documentation for the records method for
59
- # information on how these two methods differ.
69
+ # Returns the record at <tt>index</tt>.
60
70
  def record(index)
61
- seek_to_record(index)
62
- active_record? ? Record.new(self, @data_file, @memo_file) : nil
71
+ if in_memory?
72
+ records[index]
73
+ else
74
+ get_record_from_file(index)
75
+ end
76
+ end
77
+
78
+ # Find records. Examples:
79
+ # reader = DBF::Reader.new 'mydata.dbf'
80
+ #
81
+ # # Find record number 5
82
+ # reader.find(5)
83
+ #
84
+ # # Find all records for Keith Morrison
85
+ # reader.find :all, :first_name => "Keith", :last_name => "Morrison"
86
+ #
87
+ # # Find first record
88
+ # reader.find :first, :first_name => "Keith"
89
+ #
90
+ # The <b>command</b> can be an id, :all, or :first.
91
+ # <b>options</b> is optional and, if specified, should be a hash where the keys correspond
92
+ # to field names in the database. The values will be matched exactly with the value
93
+ # in the database. If you specify more than one key, all values must match in order
94
+ # for the record to be returned. The equivalent SQL would be "WHERE key1 = 'value1'
95
+ # AND key2 = 'value2'".
96
+ def find(command, options = {})
97
+ case command
98
+ when Fixnum
99
+ if !options.empty?
100
+ raise ArgumentError, "options are not allowed when command is a record number"
101
+ end
102
+ record(command)
103
+ when :all
104
+ records.select do |record|
105
+ options.map {|key, value| record[key.to_s] == value}.all?
106
+ end
107
+ when :first
108
+ return records.first if options.empty?
109
+ records.detect do |record|
110
+ options.map {|key, value| record[key.to_s] == value}.all?
111
+ end
112
+ end
63
113
  end
64
114
 
65
115
  alias_method :row, :record
66
116
 
117
+ # Returns a description of the current database file.
67
118
  def version_description
68
119
  VERSION_DESCRIPTIONS[version]
69
120
  end
70
121
 
122
+ # Returns a database schema in the portable ActiveRecord::Schema format.
123
+ #
124
+ # xBase data types are converted to generic types as follows:
125
+ # - Number fields are converted to :integer if there are no decimals, otherwise
126
+ # they are converted to :float
127
+ # - Date fields are converted to :datetime
128
+ # - Logical fields are converted to :boolean
129
+ # - Memo fields are converted to :text
130
+ # - Character fields are converted to :string and the :limit option is set
131
+ # to the length of the character field
132
+ #
133
+ # Example:
134
+ # create_table "mydata" do |t|
135
+ # t.column :name, :string, :limit => 30
136
+ # t.column :last_update, :datetime
137
+ # t.column :is_active, :boolean
138
+ # t.column :age, :integer
139
+ # t.column :notes, :text
140
+ # end
141
+ def schema(path = nil)
142
+ s = "ActiveRecord::Schema.define do\n"
143
+ s << " create_table \"#{File.basename(@data_file.path, ".*")}\" do |t|\n"
144
+ fields.each do |field|
145
+ s << " t.column \"#{field.name}\""
146
+ case field.type
147
+ when "N" # number
148
+ if field.decimal > 0
149
+ s << ", :float"
150
+ else
151
+ s << ", :integer"
152
+ end
153
+ when "D" # date
154
+ s << ", :datetime"
155
+ when "L" # boolean
156
+ s << ", :boolean"
157
+ when "M" # memo
158
+ s << ", :text"
159
+ else
160
+ s << ", :string, :limit => #{field.length}"
161
+ end
162
+ s << "\n"
163
+ end
164
+ s << " end\nend"
165
+
166
+ if path
167
+ return File.open(path, 'w') {|f| f.puts(s)}
168
+ else
169
+ return s
170
+ end
171
+ end
172
+
71
173
  private
72
174
 
73
- # Returns false if the record has been marked as deleted, otherwise it returns true. When dBase records are deleted a
74
- # flag is set, marking the record as deleted. The record will not be fully removed until the database has been compacted.
75
- def active_record?
76
- @data_file.read(1).unpack('H2').to_s == '20'
77
- rescue
78
- false
79
- end
175
+ def open_memo(file)
176
+ %w(fpt FPT dbt DBT).each do |extension|
177
+ filename = file.sub(/#{File.extname(file)[1..-1]}$/, extension)
178
+ if File.exists?(filename)
179
+ @memo_file_format = extension.downcase.to_sym
180
+ return File.open(filename, 'rb')
181
+ end
182
+ end
183
+ nil
184
+ end
80
185
 
81
- def get_header_info
82
- @data_file.rewind
83
- @version, @record_count, @header_length, @record_length = @data_file.read(DBF_HEADER_SIZE).unpack('H2xxxVvv')
84
- @field_count = (@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE
85
- end
186
+ # Returns false if the record has been marked as deleted, otherwise it returns true. When dBase records are deleted a
187
+ # flag is set, marking the record as deleted. The record will not be fully removed until the database has been compacted.
188
+ def active_record?
189
+ @data_file.read(1).unpack('H2').to_s == '20'
190
+ rescue
191
+ false
192
+ end
193
+
194
+ def get_header_info
195
+ @data_file.rewind
196
+ @version, @record_count, @header_length, @record_length = @data_file.read(DBF_HEADER_SIZE).unpack('H2xxxVvv')
197
+ @field_count = (@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE
198
+ end
86
199
 
87
- def get_field_descriptors
88
- @fields = []
89
- @field_count.times do
90
- name, type, length, decimal = @data_file.read(32).unpack('a10xax4CC')
91
- if length > 0 && !name.strip.empty?
92
- @fields << Field.new(name, type, length, decimal)
200
+ def get_field_descriptors
201
+ @fields = []
202
+ @field_count.times do
203
+ name, type, length, decimal = @data_file.read(32).unpack('a10xax4CC')
204
+ if length > 0 && !name.strip.empty?
205
+ @fields << Field.new(name, type, length, decimal)
206
+ end
93
207
  end
208
+ # adjust field count
209
+ @field_count = @fields.size
210
+ @fields
94
211
  end
95
- # adjust field count
96
- @field_count = @fields.size
97
- @fields
98
- end
99
212
 
100
- def get_memo_header_info
101
- @memo_file.rewind
102
- if @memo_file_format == :fpt
103
- @memo_next_available_block, @memo_block_size = @memo_file.read(FPT_HEADER_SIZE).unpack('Nxxn')
104
- else
105
- @memo_block_size = 512
106
- @memo_next_available_block = File.size(@memo_file.path) / @memo_block_size
213
+ def get_memo_header_info
214
+ @memo_file.rewind
215
+ if @memo_file_format == :fpt
216
+ @memo_next_available_block, @memo_block_size = @memo_file.read(FPT_HEADER_SIZE).unpack('Nxxn')
217
+ else
218
+ @memo_block_size = 512
219
+ @memo_next_available_block = File.size(@memo_file.path) / @memo_block_size
220
+ end
107
221
  end
108
- end
109
222
 
110
- def seek(offset)
111
- @data_file.seek(@header_length + offset)
112
- end
223
+ def seek(offset)
224
+ @data_file.seek(@header_length + offset)
225
+ end
113
226
 
114
- def seek_to_record(index)
115
- seek(index * @record_length)
116
- end
227
+ def seek_to_record(index)
228
+ seek(index * @record_length)
229
+ end
230
+
231
+ # Returns the record at <tt>index</tt> by seeking to the record in the
232
+ # physical database file. See the documentation for the records method for
233
+ # information on how these two methods differ.
234
+ def get_record_from_file(index)
235
+ seek_to_record(index)
236
+ active_record? ? Record.new(self, @data_file, @memo_file) : nil
237
+ end
238
+
239
+ def get_all_records_from_file
240
+ seek_to_record(0)
241
+ Array.new(@record_count) do |i|
242
+ active_record? ? DBF::Record.new(self, @data_file, @memo_file) : nil
243
+ end
244
+ end
117
245
 
118
246
  end
119
247
 
data/lib/dbf/record.rb CHANGED
@@ -21,7 +21,7 @@ module DBF
21
21
  when 'L' # logical
22
22
  self[field.name] = unpack_string(field) =~ /^(y|t)$/i ? true : false
23
23
  else
24
- self[field.name] = unpack_string(field)
24
+ self[field.name] = unpack_string(field).strip
25
25
  end
26
26
  end
27
27
  self
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe DBF::Field, "when initialized" do
4
+
5
+ before(:each) do
6
+ @field = DBF::Field.new "FieldName", "N", 1, 0
7
+ end
8
+
9
+ it "should set the 'name' accessor" do
10
+ @field.name.should == "FieldName"
11
+ end
12
+
13
+ it "should set the 'type' accessor" do
14
+ @field.type.should == "N"
15
+ end
16
+
17
+ it "should set the 'length' accessor" do
18
+ @field.length.should == 1
19
+ end
20
+
21
+ it "should set the 'decimal' accessor" do
22
+ @field.decimal.should == 0
23
+ end
24
+
25
+ it "should raise an error if length is greater than 0" do
26
+ lambda { field = DBF::Field.new "FieldName", "N", -1, 0 }.should raise_error(DBF::FieldLengthError)
27
+ end
28
+
29
+ it "should strip null characters from the name" do
30
+ field = DBF::Field.new "Field\0Name\0", "N", 1, 0
31
+ field.name.should == "FieldName"
32
+ end
33
+
34
+ end
@@ -0,0 +1,19 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table "dbase_iii_memo" do |t|
3
+ t.column "ID", :integer
4
+ t.column "CATCOUNT", :integer
5
+ t.column "AGRPCOUNT", :integer
6
+ t.column "PGRPCOUNT", :integer
7
+ t.column "ORDER", :integer
8
+ t.column "CODE", :string, :limit => 50
9
+ t.column "NAME", :string, :limit => 100
10
+ t.column "THUMBNAIL", :string, :limit => 254
11
+ t.column "IMAGE", :string, :limit => 254
12
+ t.column "PRICE", :float
13
+ t.column "COST", :float
14
+ t.column "DESC", :text
15
+ t.column "WEIGHT", :float
16
+ t.column "TAXABLE", :boolean
17
+ t.column "ACTIVE", :boolean
18
+ end
19
+ end
@@ -0,0 +1,142 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe DBF::Reader, "when initialized" do
4
+
5
+ before(:all) do
6
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
7
+ end
8
+
9
+ it "should load the data file" do
10
+ @reader.instance_eval("@data_file").should be_kind_of(File)
11
+ end
12
+
13
+ it "should locate load the memo file" do
14
+ @reader.has_memo_file?.should be_true
15
+ @reader.instance_eval("@memo_file").should be_kind_of(File)
16
+ end
17
+
18
+ it "should determine the memo file format" do
19
+ @reader.memo_file_format.should == :dbt
20
+ end
21
+
22
+ it "should determine the memo block size" do
23
+ @reader.memo_block_size.should == 512
24
+ end
25
+
26
+ it "should default to loading all records into memory" do
27
+ @reader.in_memory?.should be_true
28
+ end
29
+
30
+ it "should determine the number of fields in each record" do
31
+ @reader.fields.size.should == 15
32
+ end
33
+
34
+ it "should determine the number of records in the database" do
35
+ @reader.record_count.should == 67
36
+ end
37
+
38
+ it "should determine the database version" do
39
+ @reader.version.should == "83"
40
+ end
41
+
42
+ end
43
+
44
+ describe DBF::Reader, "when the in_memory flag is true" do
45
+
46
+ before(:each) do
47
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
48
+ end
49
+
50
+ it "should build the records array from disk only on the first request" do
51
+ @reader.expects(:get_all_records_from_file).at_most_once.returns([])
52
+ 3.times { @reader.records }
53
+ end
54
+
55
+ it "should read from the records array when using the record() method" do
56
+ @reader.expects(:get_all_records_from_file).at_most_once.returns([])
57
+ @reader.expects(:get_record_from_file).never
58
+ @reader.expects(:records).times(2).returns([])
59
+ @reader.record(1)
60
+ @reader.record(10)
61
+ end
62
+
63
+ end
64
+
65
+ describe DBF::Reader, "when the in_memory flag is false" do
66
+
67
+ before(:each) do
68
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
69
+ end
70
+
71
+ it "should read the records from disk on every request" do
72
+ @reader.in_memory = false
73
+ @reader.expects(:get_all_records_from_file).times(3).returns([])
74
+ 3.times { @reader.records }
75
+ end
76
+ end
77
+
78
+ describe DBF::Reader, "schema" do
79
+
80
+ it "should match test schema " do
81
+ reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
82
+ control_schema = File.read(File.dirname(__FILE__) + '/fixtures/dbase_iii_memo_schema.rb')
83
+
84
+ reader.schema.should == control_schema
85
+ end
86
+
87
+ end
88
+
89
+ describe DBF::Reader, "find(index)" do
90
+
91
+ before(:all) do
92
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
93
+ end
94
+
95
+ it "should return the correct record" do
96
+ @reader.find(5).should == @reader.record(5)
97
+ end
98
+
99
+ it "should raise an error if options are not empty" do
100
+ lambda { @reader.find(5, :name => 'test') }.should raise_error(ArgumentError)
101
+ end
102
+
103
+ end
104
+
105
+ describe DBF::Reader, "find(:all)" do
106
+
107
+ before(:all) do
108
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
109
+ end
110
+
111
+ it "should return all records if options are empty" do
112
+ @reader.find(:all).should == @reader.records
113
+ end
114
+
115
+ it "should return matching records when used with options" do
116
+ @reader.find(:all, "WEIGHT" => 0.0).should == @reader.records.select {|r| r["WEIGHT"] == 0.0}
117
+ end
118
+
119
+ it "with multiple options should search for all search terms as if using AND" do
120
+ @reader.find(:all, "ID" => 30, "IMAGE" => "graphics/00000001/TBC01.jpg").should == []
121
+ end
122
+ end
123
+
124
+ describe DBF::Reader, "find(:first)" do
125
+
126
+ before(:all) do
127
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
128
+ end
129
+
130
+ it "should return the first record if options are empty" do
131
+ @reader.find(:first).should == @reader.records.first
132
+ end
133
+
134
+ it "should return the first matching record when used with options" do
135
+ @reader.find(:first, "CODE" => "C").should == @reader.record(5)
136
+ end
137
+
138
+ it "with multiple options should search for all search terms as if using AND" do
139
+ @reader.find(:first, "ID" => 30, "IMAGE" => "graphics/00000001/TBC01.jpg").should be_nil
140
+ end
141
+ end
142
+
@@ -0,0 +1,9 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe DBF::Record, "when initialized" do
4
+
5
+ it "should convert number fields with 0 decimals to integers" do
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+ require "rubygems"
3
+ require "spec"
4
+ require "dbf"
5
+
6
+ Spec::Runner.configure do |config|
7
+ config.mock_with :mocha
8
+ end
Binary file
Binary file
@@ -0,0 +1,27 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+ require 'test/unit'
3
+ require 'dbf'
4
+ require 'common'
5
+
6
+ class DBaseIIIWithMemoReadTest < Test::Unit::TestCase
7
+ include CommonTests::Read
8
+
9
+ def setup
10
+ @controls = {
11
+ :version => "83",
12
+ :has_memo_file => true,
13
+ :memo_file_format => :dbt,
14
+ :field_count => 15,
15
+ :record_count => 67,
16
+ :record_length => 805,
17
+ :testable_character_field_names => ["CODE"],
18
+ :testable_date_field_names => [],
19
+ :testable_integer_field_names => ["AGRPCOUNT"],
20
+ :testable_float_field_names => ["PRICE"],
21
+ :testable_logical_field_names => ["TAXABLE"],
22
+ :testable_memo_field_names => ["DESC"]
23
+ }
24
+ @dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'dbase_iii_memo.dbf'))
25
+ end
26
+
27
+ end
@@ -0,0 +1,27 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+ require 'test/unit'
3
+ require 'dbf'
4
+ require 'common'
5
+
6
+ class VisualFoxproReadTest < Test::Unit::TestCase
7
+ include CommonTests::Read
8
+
9
+ def setup
10
+ @controls = {
11
+ :version => "30",
12
+ :has_memo_file => true,
13
+ :memo_file_format => :fpt,
14
+ :field_count => 145,
15
+ :record_count => 34,
16
+ :record_length => 3907,
17
+ :testable_character_field_names => [],
18
+ :testable_date_field_names => [],
19
+ :testable_integer_field_names => ["IMAGENO"],
20
+ :testable_float_field_names => [],
21
+ :testable_logical_field_names => [],
22
+ :testable_memo_field_names => ["CREDIT"]
23
+ }
24
+ @dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'visual_foxpro.dbf'))
25
+ end
26
+
27
+ end
metadata CHANGED
@@ -3,8 +3,8 @@ 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
7
- date: 2007-05-25 00:00:00 -07:00
6
+ version: 0.5.0
7
+ date: 2007-05-26 00:00:00 -07:00
8
8
  summary: A small fast library for reading dBase, xBase, Clipper and FoxPro database files.
9
9
  require_paths:
10
10
  - lib
@@ -29,31 +29,43 @@ post_install_message:
29
29
  authors:
30
30
  - Keith Morrison
31
31
  files:
32
- - Rakefile
32
+ - History.txt
33
+ - Manifest.txt
33
34
  - README.txt
34
- - LICENSE.txt
35
+ - Rakefile
36
+ - benchmarks/performance.rb
37
+ - benchmarks/seek_benchmark.rb
35
38
  - lib/dbf.rb
36
39
  - lib/dbf/field.rb
37
40
  - lib/dbf/globals.rb
38
41
  - lib/dbf/reader.rb
39
42
  - lib/dbf/record.rb
43
+ - spec/field_spec.rb
44
+ - spec/fixtures/dbase_iii_memo_schema.rb
45
+ - spec/reader_spec.rb
46
+ - spec/record_spec.rb
47
+ - spec/spec_helper.rb
40
48
  - test/common.rb
41
- - test/dbase_iii_read_test.rb
42
- - test/foxpro_read_test.rb
43
- - test/performance.rb
44
49
  - test/databases/dbase_iii.dbf
50
+ - test/databases/dbase_iii_memo.dbf
51
+ - test/databases/dbase_iii_memo.dbt
45
52
  - test/databases/foxpro.dbf
46
53
  - test/databases/foxpro.fpt
47
54
  - test/databases/visual_foxpro.dbf
48
55
  - test/databases/visual_foxpro.fpt
56
+ - test/dbase_iii_memo_read_test.rb
57
+ - test/dbase_iii_read_test.rb
58
+ - test/foxpro_read_test.rb
59
+ - test/visual_foxpro_read_test.rb
49
60
  test_files: []
50
61
 
51
62
  rdoc_options:
52
63
  - --main
53
64
  - README.txt
54
65
  extra_rdoc_files:
66
+ - History.txt
67
+ - Manifest.txt
55
68
  - README.txt
56
- - LICENSE.txt
57
69
  executables: []
58
70
 
59
71
  extensions: []
data/LICENSE.txt DELETED
@@ -1,22 +0,0 @@
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.