dbf 1.7.1 → 1.7.2

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 CHANGED
@@ -1,3 +1,7 @@
1
+ # 1.7.2
2
+ - Fix integer division under Ruby 1.8 when requiring mathn
3
+ standard library (see http://bugs.ruby-lang.org/issues/2121)
4
+
1
5
  # 1.7.1
2
6
  - Fix Table.FOXPRO_VERSIONS breakage on Ruby 1.8
3
7
 
data/Gemfile.lock CHANGED
@@ -1,20 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dbf (1.7.0)
4
+ dbf (1.7.2)
5
5
  fastercsv (~> 1.5.4)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- archive-tar-minitar (0.5.2)
11
- columnize (0.3.4)
12
10
  diff-lcs (1.1.3)
13
11
  fastercsv (1.5.4)
14
- linecache19 (0.5.12)
15
- ruby_core_source (>= 0.1.4)
12
+ json (1.6.3)
13
+ json (1.6.3-java)
16
14
  rake (0.9.2.2)
17
- rdoc (3.9.4)
15
+ rdoc (3.11)
16
+ json (~> 1.4)
18
17
  rspec (2.7.0)
19
18
  rspec-core (~> 2.7.0)
20
19
  rspec-expectations (~> 2.7.0)
@@ -23,23 +22,13 @@ GEM
23
22
  rspec-expectations (2.7.0)
24
23
  diff-lcs (~> 1.1.2)
25
24
  rspec-mocks (2.7.0)
26
- ruby-debug-base19 (0.11.25)
27
- columnize (>= 0.3.1)
28
- linecache19 (>= 0.5.11)
29
- ruby_core_source (>= 0.1.4)
30
- ruby-debug19 (0.11.6)
31
- columnize (>= 0.3.1)
32
- linecache19 (>= 0.5.11)
33
- ruby-debug-base19 (>= 0.11.19)
34
- ruby_core_source (0.1.5)
35
- archive-tar-minitar (>= 0.5.2)
36
25
 
37
26
  PLATFORMS
27
+ java
38
28
  ruby
39
29
 
40
30
  DEPENDENCIES
41
31
  dbf!
42
32
  rake (~> 0.9.2)
43
- rdoc (~> 3.9.0)
33
+ rdoc (~> 3.11)
44
34
  rspec (~> 2.7.0)
45
- ruby-debug19
data/README.md CHANGED
@@ -11,7 +11,13 @@ database files
11
11
 
12
12
  ## Compatibility
13
13
 
14
- DBF is tested to work with MRI Ruby 1.8.6, 1.8.7, 1.9.2, 1.9.3 and Jruby 1.6.2
14
+ DBF is tested to work with the following versions of ruby:
15
+
16
+ * MRI Ruby 1.8.6, 1.8.7, 1.9.1, 1.9.2 and 1.9.3
17
+ * JRuby 1.6.2, 1.6.3, 1.6.4, and 1.6.5
18
+ * REE 1.8.6, 1.8.7
19
+
20
+ [![Build Status](https://secure.travis-ci.org/infused/dbf.png)](http://travis-ci.org/infused/dbf)
15
21
 
16
22
  ## Installation
17
23
 
data/Rakefile CHANGED
@@ -6,7 +6,11 @@ Bundler.setup(:default, :development)
6
6
 
7
7
  require 'rspec/core/rake_task'
8
8
  RSpec::Core::RakeTask.new :spec do |t|
9
- t.rspec_opts = %w(-fs --color)
9
+ t.rspec_opts = %w(--color)
10
+ end
11
+
12
+ RSpec::Core::RakeTask.new :specdoc do |t|
13
+ t.rspec_opts = %w(-fl)
10
14
  end
11
15
 
12
16
  require 'rake'
@@ -30,4 +34,31 @@ end
30
34
  # MetricFu::Configuration.run do |config|
31
35
  # config.rcov[:test_files] = ['spec/**/*_spec.rb']
32
36
  # config.rcov[:rcov_opts] << "-Ispec"
33
- # end
37
+ # end
38
+
39
+ namespace :test do
40
+ task :rubies do
41
+ require File.expand_path('spec/rvm_ruby_runner', File.dirname(__FILE__))
42
+
43
+ current_rvm = `rvm info`.lines.to_a[1]
44
+
45
+ rubies = %w(
46
+ ree-1.8.6
47
+ ree-1.8.7
48
+ jruby-1.6.2
49
+ jruby-1.6.3
50
+ jruby-1.6.4
51
+ jruby-1.6.5
52
+ ruby-1.8.6
53
+ ruby-1.8.7
54
+ ruby-1.9.1
55
+ ruby-1.9.2
56
+ ruby-1.9.3
57
+ )
58
+ rubies.each do |version|
59
+ puts RvmRubyRunner.run(version)
60
+ end
61
+
62
+ `rvm use #{current_rvm}`
63
+ end
64
+ end
data/dbf.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+ require 'dbf/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'dbf'
7
+ s.version = DBF::VERSION
8
+ s.authors = ['Keith Morrison']
9
+ s.email = 'keithm@infused.org'
10
+ s.homepage = 'http://github.com/infused/dbf'
11
+ s.summary = 'Read xBase files'
12
+ s.description = 'A small fast library for reading dBase, xBase, Clipper and FoxPro database files.'
13
+
14
+ s.executables = ['dbf']
15
+ s.rdoc_options = ['--charset=UTF-8']
16
+ s.extra_rdoc_files = ['README.md', 'CHANGELOG.md', 'MIT-LICENSE']
17
+ s.files = Dir['[A-Z]*', '{bin,docs,lib,spec}/**/*', 'dbf.gemspec']
18
+ s.test_files = Dir.glob('spec/**/*_spec.rb')
19
+ s.require_paths = ['lib']
20
+
21
+ s.required_rubygems_version = '>= 1.3.0'
22
+ s.add_dependency 'fastercsv', '~> 1.5.4'
23
+ s.add_development_dependency 'rspec', '~> 2.7.0'
24
+ s.add_development_dependency 'rake', '~> 0.9.2'
25
+
26
+ if RUBY_VERSION == '1.8.6'
27
+ s.add_development_dependency 'rdoc', '~> 2.5.0'
28
+ else
29
+ s.add_development_dependency 'rdoc', '~> 3.11'
30
+ end
31
+
32
+ # if RUBY_VERSION.to_f >= 1.9
33
+ # s.add_development_dependency 'ruby-debug19'
34
+ # elsif RUBY_VERSION != '1.8.6'
35
+ # s.add_development_dependency 'ruby-debug'
36
+ # end
37
+ # s.add_development_dependency 'metric_fu'
38
+ end
39
+
data/lib/dbf.rb CHANGED
@@ -9,7 +9,11 @@ end
9
9
  require 'dbf/util'
10
10
  require 'dbf/attributes'
11
11
  require 'dbf/record'
12
- require 'dbf/column'
13
- require 'dbf/foxpro_column'
12
+ require 'dbf/column/base'
13
+ require 'dbf/column/dbase'
14
+ require 'dbf/column/foxpro'
14
15
  require 'dbf/table'
15
- require 'dbf/memo'
16
+ require 'dbf/memo/base'
17
+ require 'dbf/memo/dbase3'
18
+ require 'dbf/memo/dbase4'
19
+ require 'dbf/memo/foxpro'
@@ -0,0 +1,122 @@
1
+ module DBF
2
+ module Column
3
+ class LengthError < StandardError; end
4
+ class NameError < StandardError; end
5
+
6
+ class Base
7
+ attr_reader :name, :type, :length, :decimal
8
+
9
+ # Initialize a new DBF::Column
10
+ #
11
+ # @param [String] name
12
+ # @param [String] type
13
+ # @param [Fixnum] length
14
+ # @param [Fixnum] decimal
15
+ def initialize(name, type, length, decimal, version, encoding=nil)
16
+ @name, @type, @length, @decimal, @version, @encoding = clean(name), type, length, decimal, version, encoding
17
+
18
+ raise LengthError, "field length must be greater than 0" unless length > 0
19
+ raise NameError, "column name cannot be empty" if @name.length == 0
20
+ end
21
+
22
+ # Cast value to native type
23
+ #
24
+ # @param [String] value
25
+ # @return [Fixnum, Float, Date, DateTime, Boolean, String]
26
+ def type_cast(value)
27
+ case type
28
+ when 'N' then unpack_number(value)
29
+ when 'I' then unpack_unsigned_long(value)
30
+ when 'F' then value.to_f
31
+ when 'D' then decode_date(value)
32
+ when 'T' then decode_datetime(value)
33
+ when 'L' then boolean(value)
34
+ when 'B' then unpack_binary(value)
35
+ else encode_string(value.to_s).strip
36
+ end
37
+ end
38
+
39
+ def memo?
40
+ @memo ||= type == 'M'
41
+ end
42
+
43
+ # Schema definition
44
+ #
45
+ # @return [String]
46
+ def schema_definition
47
+ "\"#{underscored_name}\", #{schema_data_type}\n"
48
+ end
49
+
50
+ def underscored_name
51
+ @underscored_name ||= Util.underscore(name)
52
+ end
53
+
54
+ private
55
+
56
+ def decode_date(value) #nodoc
57
+ value.gsub!(' ', '0')
58
+ value !~ /\S/ ? nil : Date.parse(value)
59
+ rescue
60
+ nil
61
+ end
62
+
63
+ def decode_datetime(value) #nodoc
64
+ days, milliseconds = value.unpack('l2')
65
+ seconds = (milliseconds / 1000).to_i
66
+ DateTime.jd(days, (seconds/3600).to_i, (seconds/60).to_i % 60, seconds % 60) rescue nil
67
+ end
68
+
69
+ def unpack_number(value) #nodoc
70
+ decimal.zero? ? value.to_i : value.to_f
71
+ end
72
+
73
+ def unpack_unsigned_long(value) #nodoc
74
+ value.unpack('V')[0]
75
+ end
76
+
77
+ def unpack_binary(value) #nodoc
78
+ end
79
+
80
+ def boolean(value) #nodoc
81
+ value.strip =~ /^(y|t)$/i ? true : false
82
+ end
83
+
84
+ def encode_string(value)
85
+ @encoding ? value.force_encoding(@encoding).encode(Encoding.default_external, :undef => :replace, :invalid => :replace) : value
86
+ end
87
+
88
+ def schema_data_type #nodoc
89
+ case type
90
+ when "N", "F"
91
+ decimal > 0 ? ":float" : ":integer"
92
+ when "I"
93
+ ":integer"
94
+ when "D"
95
+ ":date"
96
+ when "T"
97
+ ":datetime"
98
+ when "L"
99
+ ":boolean"
100
+ when "M"
101
+ ":text"
102
+ when "B"
103
+ if DBF::Table::FOXPRO_VERSIONS.keys.include?(@version)
104
+ decimal > 0 ? ":float" : ":integer"
105
+ else
106
+ ":text"
107
+ end
108
+ else
109
+ ":string, :limit => #{length}"
110
+ end
111
+ end
112
+
113
+ def clean(value) #nodoc
114
+ first_null = value.index("\x00")
115
+ value = value[0, first_null] if first_null
116
+ value.gsub(/[^\x20-\x7E]/, "")
117
+ end
118
+
119
+
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,7 @@
1
+ module DBF
2
+ module Column
3
+ class Dbase < Base
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module DBF
2
+ module Column
3
+ class Foxpro < Base
4
+ def unpack_binary(value) #nodoc
5
+ value.unpack('d')[0]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ module DBF
2
+ module Memo
3
+ class Base
4
+ BLOCK_HEADER_SIZE = 8
5
+
6
+ def self.open(filename, version)
7
+ self.new File.open(filename, 'rb'), version
8
+ end
9
+
10
+ def initialize(data, version)
11
+ @data, @version = data, version
12
+ end
13
+
14
+ def get(start_block)
15
+ if start_block > 0
16
+ build_memo start_block
17
+ end
18
+ end
19
+
20
+ def close
21
+ @data.close
22
+ end
23
+
24
+ private
25
+
26
+ def offset(start_block) #nodoc
27
+ start_block * block_size
28
+ end
29
+
30
+ def content_size(memo_size) #nodoc
31
+ (memo_size - block_size) + BLOCK_HEADER_SIZE
32
+ end
33
+
34
+ def block_content_size #nodoc
35
+ @block_content_size ||= block_size - BLOCK_HEADER_SIZE
36
+ end
37
+
38
+ def block_size #nodoc
39
+ 512
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,15 @@
1
+ module DBF
2
+ module Memo
3
+ class Dbase3 < Base
4
+ def build_memo(start_block) #nodoc
5
+ @data.seek offset(start_block)
6
+ memo_string = ""
7
+ begin
8
+ block = @data.read(block_size).gsub(/(\000|\032)/, '')
9
+ memo_string << block
10
+ end until block.size < block_size
11
+ memo_string
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module DBF
2
+ module Memo
3
+ class Dbase4 < Base
4
+ def build_memo(start_block) #nodoc
5
+ @data.seek offset(start_block)
6
+ @data.read(@data.read(BLOCK_HEADER_SIZE).unpack("x4L").first)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ module DBF
2
+ module Memo
3
+ class Foxpro < Base
4
+ FPT_HEADER_SIZE = 512
5
+
6
+ def build_memo(start_block) #nodoc
7
+ @data.seek offset(start_block)
8
+
9
+ memo_type, memo_size, memo_string = @data.read(block_size).unpack("NNa*")
10
+ return nil unless memo_type == 1 && memo_size > 0
11
+
12
+ if memo_size > block_content_size
13
+ memo_string << @data.read(content_size(memo_size))
14
+ else
15
+ memo_string = memo_string[0, memo_size]
16
+ end
17
+ memo_string
18
+ end
19
+
20
+ private
21
+
22
+ def block_size #nodoc
23
+ @block_size ||= begin
24
+ @data.rewind
25
+ @data.read(FPT_HEADER_SIZE).unpack('x6n').first || 0
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/dbf/table.rb CHANGED
@@ -199,35 +199,51 @@ module DBF
199
199
  def supports_encoding?
200
200
  "".respond_to? :encoding
201
201
  end
202
+
203
+ def foxpro?
204
+ FOXPRO_VERSIONS.keys.include? @version
205
+ end
202
206
 
203
207
  private
204
208
 
205
209
  def column_class #nodoc
206
- @column_class ||= if FOXPRO_VERSIONS.keys.include?(version)
207
- FoxproColumn
208
- else
209
- Column
210
+ @column_class ||= if foxpro?
211
+ Column::Foxpro
212
+ else
213
+ Column::Dbase
214
+ end
215
+ end
216
+
217
+ def memo_class #nodoc
218
+ @memo_class ||= if foxpro?
219
+ Memo::Foxpro
220
+ else
221
+ if @version == "83"
222
+ Memo::Dbase3
223
+ else
224
+ Memo::Dbase4
225
+ end
210
226
  end
211
227
  end
212
228
 
213
229
  def column_count #nodoc
214
- @column_count ||= (@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE
230
+ @column_count ||= ((@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE).to_i
215
231
  end
216
232
 
217
- def open_data(data)
233
+ def open_data(data) #nodoc
218
234
  data.is_a?(StringIO) ? data : File.open(data, 'rb')
219
235
  end
220
236
 
221
237
  def open_memo(data, memo = nil) #nodoc
222
238
  if memo.is_a? StringIO
223
- DBF::Memo.new(memo, version)
239
+ memo_class.new(memo, version)
224
240
  elsif memo
225
- DBF::Memo.open(memo, version)
241
+ memo_class.open(memo, version)
226
242
  elsif !data.is_a? StringIO
227
243
  dirname = File.dirname(data)
228
244
  basename = File.basename(data, '.*')
229
245
  files = Dir.glob("#{dirname}/#{basename}*.{fpt,FPT,dbt,DBT}")
230
- files.any? ? DBF::Memo.open(files.first, version) : nil
246
+ files.any? ? memo_class.open(files.first, version) : nil
231
247
  else
232
248
  nil
233
249
  end
data/lib/dbf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module DBF
2
- VERSION = '1.7.1'
2
+ VERSION = '1.7.2'
3
3
  end
@@ -1,9 +1,9 @@
1
1
  require "spec_helper"
2
2
 
3
- describe DBF::Column do
3
+ describe DBF::Column::Dbase do
4
4
 
5
5
  context "when initialized" do
6
- let(:column) { DBF::Column.new "ColumnName", "N", 1, 0, "30" }
6
+ let(:column) { DBF::Column::Dbase.new "ColumnName", "N", 1, 0, "30" }
7
7
 
8
8
  it "sets the #name accessor" do
9
9
  column.name.should == "ColumnName"
@@ -22,15 +22,15 @@ describe DBF::Column do
22
22
  end
23
23
 
24
24
  describe 'with length of 0' do
25
- specify { lambda { DBF::Column.new "ColumnName", "N", 0, 0, "30" }.should raise_error(DBF::ColumnLengthError) }
25
+ specify { lambda { DBF::Column::Dbase.new "ColumnName", "N", 0, 0, "30" }.should raise_error(DBF::Column::LengthError) }
26
26
  end
27
27
 
28
28
  describe 'with length less than 0' do
29
- specify { lambda { DBF::Column.new "ColumnName", "N", -1, 0, "30" }.should raise_error(DBF::ColumnLengthError) }
29
+ specify { lambda { DBF::Column::Dbase.new "ColumnName", "N", -1, 0, "30" }.should raise_error(DBF::Column::LengthError) }
30
30
  end
31
31
 
32
32
  describe 'with empty column name' do
33
- specify { lambda { DBF::Column.new "\xFF\xFC", "N", 1, 0, "30" }.should raise_error(DBF::ColumnNameError) }
33
+ specify { lambda { DBF::Column::Dbase.new "\xFF\xFC", "N", 1, 0, "30" }.should raise_error(DBF::Column::NameError) }
34
34
  end
35
35
  end
36
36
 
@@ -39,8 +39,8 @@ describe DBF::Column do
39
39
  context 'and 0 decimals' do
40
40
  it 'casts value to Fixnum' do
41
41
  value = '135'
42
- column = DBF::Column.new "ColumnName", "N", 3, 0, "30"
43
- column.type_cast(value).should be_a Fixnum
42
+ column = DBF::Column::Dbase.new "ColumnName", "N", 3, 0, "30"
43
+ column.type_cast(value).should be_a(Fixnum)
44
44
  column.type_cast(value).should == 135
45
45
  end
46
46
  end
@@ -48,8 +48,8 @@ describe DBF::Column do
48
48
  context 'and more than 0 decimals' do
49
49
  it 'casts value to Float' do
50
50
  value = '13.5'
51
- column = DBF::Column.new "ColumnName", "N", 2, 1, "30"
52
- column.type_cast(value).should be_a Float
51
+ column = DBF::Column::Dbase.new "ColumnName", "N", 2, 1, "30"
52
+ column.type_cast(value).should be_a(Float)
53
53
  column.type_cast(value).should == 13.5
54
54
  end
55
55
  end
@@ -58,8 +58,8 @@ describe DBF::Column do
58
58
  context 'with type F (float)' do
59
59
  it 'casts value to Float' do
60
60
  value = '135'
61
- column = DBF::Column.new "ColumnName", "F", 3, 0, "30"
62
- column.type_cast(value).should be_a Float
61
+ column = DBF::Column::Dbase.new "ColumnName", "F", 3, 0, "30"
62
+ column.type_cast(value).should be_a(Float)
63
63
  column.type_cast(value).should == 135.0
64
64
  end
65
65
  end
@@ -67,13 +67,13 @@ describe DBF::Column do
67
67
  context 'with type I (integer)' do
68
68
  it "casts value to Fixnum" do
69
69
  value = "\203\171\001\000"
70
- column = DBF::Column.new "ColumnName", "I", 3, 0, "30"
70
+ column = DBF::Column::Dbase.new "ColumnName", "I", 3, 0, "30"
71
71
  column.type_cast(value).should == 96643
72
72
  end
73
73
  end
74
74
 
75
75
  context 'with type L (logical/boolean)' do
76
- let(:column) { DBF::Column.new "ColumnName", "L", 1, 0, "30" }
76
+ let(:column) { DBF::Column::Dbase.new "ColumnName", "L", 1, 0, "30" }
77
77
 
78
78
  it "casts 'y' to true" do
79
79
  column.type_cast('y').should == true
@@ -89,7 +89,7 @@ describe DBF::Column do
89
89
  end
90
90
 
91
91
  context 'with type T (datetime)' do
92
- let(:column) { DBF::Column.new "ColumnName", "T", 16, 0, "30" }
92
+ let(:column) { DBF::Column::Dbase.new "ColumnName", "T", 16, 0, "30" }
93
93
 
94
94
  context 'with valid datetime' do
95
95
  it "casts to DateTime" do
@@ -97,6 +97,15 @@ describe DBF::Column do
97
97
  end
98
98
  end
99
99
 
100
+ context 'when requiring mathn' do
101
+ it "casts to DateTime" do
102
+ lambda do
103
+ require 'mathn'
104
+ column.type_cast("Nl%\000\300Z\252\003")
105
+ end.call.should == DateTime.parse("2002-10-10T17:04:56+00:00")
106
+ end
107
+ end
108
+
100
109
  context 'with invalid datetime' do
101
110
  it "casts to nil" do
102
111
  column.type_cast("Nl%\000\000A\000\999").should be_nil
@@ -105,7 +114,7 @@ describe DBF::Column do
105
114
  end
106
115
 
107
116
  context 'with type D (date)' do
108
- let(:column) { DBF::Column.new "ColumnName", "D", 8, 0, "30" }
117
+ let(:column) { DBF::Column::Dbase.new "ColumnName", "D", 8, 0, "30" }
109
118
 
110
119
  context 'with valid date' do
111
120
  it "casts to Date" do
@@ -122,8 +131,8 @@ describe DBF::Column do
122
131
 
123
132
  context 'with type M (memo)' do
124
133
  it "casts to string" do
125
- column = DBF::Column.new "ColumnName", "M", 3, 0, "30"
126
- column.type_cast('abc').should be_a String
134
+ column = DBF::Column::Dbase.new "ColumnName", "M", 3, 0, "30"
135
+ column.type_cast('abc').should be_a(String)
127
136
  end
128
137
  end
129
138
  end
@@ -131,7 +140,7 @@ describe DBF::Column do
131
140
  context "#schema_definition" do
132
141
  context 'with type N (number)' do
133
142
  it "outputs an integer column" do
134
- column = DBF::Column.new "ColumnName", "N", 1, 0, "30"
143
+ column = DBF::Column::Dbase.new "ColumnName", "N", 1, 0, "30"
135
144
  column.schema_definition.should == "\"column_name\", :integer\n"
136
145
  end
137
146
  end
@@ -140,13 +149,13 @@ describe DBF::Column do
140
149
  context "with Foxpro dbf" do
141
150
  context "when decimal is greater than 0" do
142
151
  it "outputs an float column" do
143
- column = DBF::Column.new "ColumnName", "B", 1, 2, "f5"
152
+ column = DBF::Column::Dbase.new "ColumnName", "B", 1, 2, "f5"
144
153
  column.schema_definition.should == "\"column_name\", :float\n"
145
154
  end
146
155
  end
147
156
 
148
157
  context "when decimal is 0" do
149
- column = DBF::Column.new "ColumnName", "B", 1, 0, "f5"
158
+ column = DBF::Column::Dbase.new "ColumnName", "B", 1, 0, "f5"
150
159
  column.schema_definition.should == "\"column_name\", :integer\n"
151
160
  end
152
161
  end
@@ -159,50 +168,50 @@ describe DBF::Column do
159
168
  end
160
169
 
161
170
  it "defines a float colmn if type is (N)umber with more than 0 decimals" do
162
- column = DBF::Column.new "ColumnName", "N", 1, 2, "30"
171
+ column = DBF::Column::Dbase.new "ColumnName", "N", 1, 2, "30"
163
172
  column.schema_definition.should == "\"column_name\", :float\n"
164
173
  end
165
174
 
166
175
  it "defines a date column if type is (D)ate" do
167
- column = DBF::Column.new "ColumnName", "D", 8, 0, "30"
176
+ column = DBF::Column::Dbase.new "ColumnName", "D", 8, 0, "30"
168
177
  column.schema_definition.should == "\"column_name\", :date\n"
169
178
  end
170
179
 
171
180
  it "defines a datetime column if type is (D)ate" do
172
- column = DBF::Column.new "ColumnName", "T", 16, 0, "30"
181
+ column = DBF::Column::Dbase.new "ColumnName", "T", 16, 0, "30"
173
182
  column.schema_definition.should == "\"column_name\", :datetime\n"
174
183
  end
175
184
 
176
185
  it "defines a boolean column if type is (L)ogical" do
177
- column = DBF::Column.new "ColumnName", "L", 1, 0, "30"
186
+ column = DBF::Column::Dbase.new "ColumnName", "L", 1, 0, "30"
178
187
  column.schema_definition.should == "\"column_name\", :boolean\n"
179
188
  end
180
189
 
181
190
  it "defines a text column if type is (M)emo" do
182
- column = DBF::Column.new "ColumnName", "M", 1, 0, "30"
191
+ column = DBF::Column::Dbase.new "ColumnName", "M", 1, 0, "30"
183
192
  column.schema_definition.should == "\"column_name\", :text\n"
184
193
  end
185
194
 
186
195
  it "defines a string column with length for any other data types" do
187
- column = DBF::Column.new "ColumnName", "X", 20, 0, "30"
196
+ column = DBF::Column::Dbase.new "ColumnName", "X", 20, 0, "30"
188
197
  column.schema_definition.should == "\"column_name\", :string, :limit => 20\n"
189
198
  end
190
199
  end
191
200
 
192
201
  context "#name" do
193
202
  it "contains only ASCII characters" do
194
- column = DBF::Column.new "--\x1F-\x68\x65\x6C\x6C\x6F world-\x80--", "N", 1, 0, "30"
203
+ column = DBF::Column::Dbase.new "--\x1F-\x68\x65\x6C\x6C\x6F world-\x80--", "N", 1, 0, "30"
195
204
  column.name.should == "---hello world---"
196
205
  end
197
206
 
198
207
  it "is truncated at the null character" do
199
- column = DBF::Column.new "--\x1F-\x68\x65\x6C\x6C\x6F \x00 world-\x80--", "N", 1, 0, "30"
208
+ column = DBF::Column::Dbase.new "--\x1F-\x68\x65\x6C\x6C\x6F \x00 world-\x80--", "N", 1, 0, "30"
200
209
  column.name.should == "---hello "
201
210
  end
202
211
  end
203
212
 
204
213
  context '#decode_date' do
205
- let(:column) { DBF::Column.new "ColumnName", "N", 1, 0, "30" }
214
+ let(:column) { DBF::Column::Dbase.new "ColumnName", "N", 1, 0, "30" }
206
215
 
207
216
  it 'is nil if value is blank' do
208
217
  column.send(:decode_date, '').should be_nil
@@ -13,7 +13,7 @@ shared_examples_for 'DBF' do
13
13
  end
14
14
 
15
15
  specify "record count should be the same as reported in the header" do
16
- @table.count.should == @table.record_count
16
+ @table.entries.size.should == @table.record_count
17
17
  end
18
18
 
19
19
  specify "column names should not be blank" do
@@ -54,7 +54,7 @@ end
54
54
 
55
55
  shared_examples_for 'Foxpro DBF' do
56
56
  specify "columns should be instances of DBF::FoxproColumn" do
57
- @table.columns.all? {|column| column.should be_an_instance_of(DBF::FoxproColumn)}
57
+ @table.columns.all? {|column| column.should be_an_instance_of(DBF::Column::Foxpro)}
58
58
  end
59
59
  end
60
60
 
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe DBF::Table do
4
4
  specify 'foxpro versions' do
5
- DBF::Table::FOXPRO_VERSIONS.keys.should == %w(30 31 f5 fb)
5
+ DBF::Table::FOXPRO_VERSIONS.keys.sort.should == %w(30 31 f5 fb).sort
6
6
  end
7
7
 
8
8
  describe '#initialize' do
@@ -167,7 +167,7 @@ describe DBF::Table do
167
167
  end
168
168
 
169
169
  it "should return the first record if options are empty" do
170
- @table.find(:first).should == @table.first
170
+ @table.find(:first).should == @table.record(0)
171
171
  end
172
172
 
173
173
  it "should return the first matching record when used with options" do
@@ -201,5 +201,18 @@ describe DBF::Table do
201
201
  specify { table.has_memo_file?.should be_true }
202
202
  end
203
203
  end
204
+
205
+ describe 'when requiring mathn' do
206
+ describe 'column_count' do
207
+ let(:table) { DBF::Table.new "#{DB_PATH}/dbase_03.dbf" }
208
+
209
+ it 'returns an integer' do
210
+ lambda do
211
+ require 'mathn'
212
+ table.send(:column_count)
213
+ end.call.should == 31
214
+ end
215
+ end
216
+ end
204
217
  end
205
218
 
@@ -0,0 +1,13 @@
1
+ class RvmRubyRunner
2
+ def self.run(ruby_string)
3
+ output = `rvm use #{ruby_string}@dbf --create; bundle install; rspec`
4
+ puts output if ENV['DEBUG=1']
5
+ if output =~ /To install do/
6
+ "#{ruby_string.rjust 12}: not installed"
7
+ elsif output =~ /Finished/m
8
+ results = output.lines.to_a[-1].strip
9
+ time = output.lines.to_a[-2].strip
10
+ "#{ruby_string.rjust 12}: #{results}, #{time}"
11
+ end
12
+ end
13
+ end
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,13 @@ require 'rspec'
4
4
 
5
5
  DB_PATH = File.dirname(__FILE__) + '/fixtures' unless defined?(DB_PATH)
6
6
 
7
+ if RUBY_VERSION == "1.8.6"
8
+ # warn 'ruby-1.8.6: defining Array#reduce as alias of Array#inject'
9
+ class Array
10
+ alias_method :reduce, :inject
11
+ end
12
+ end
13
+
7
14
  RSpec.configure do |config|
8
15
 
9
16
  end
metadata CHANGED
@@ -1,82 +1,96 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: dbf
3
- version: !ruby/object:Gem::Version
4
- version: 1.7.1
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15
5
5
  prerelease:
6
+ segments:
7
+ - 1
8
+ - 7
9
+ - 2
10
+ version: 1.7.2
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Keith Morrison
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2011-12-09 00:00:00.000000000Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2011-12-28 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
15
21
  name: fastercsv
16
- requirement: &70200311059140 !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
17
24
  none: false
18
- requirements:
25
+ requirements:
19
26
  - - ~>
20
- - !ruby/object:Gem::Version
27
+ - !ruby/object:Gem::Version
28
+ hash: 11
29
+ segments:
30
+ - 1
31
+ - 5
32
+ - 4
21
33
  version: 1.5.4
22
34
  type: :runtime
23
- prerelease: false
24
- version_requirements: *70200311059140
25
- - !ruby/object:Gem::Dependency
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
26
37
  name: rspec
27
- requirement: &70200311058680 !ruby/object:Gem::Requirement
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
28
40
  none: false
29
- requirements:
41
+ requirements:
30
42
  - - ~>
31
- - !ruby/object:Gem::Version
43
+ - !ruby/object:Gem::Version
44
+ hash: 19
45
+ segments:
46
+ - 2
47
+ - 7
48
+ - 0
32
49
  version: 2.7.0
33
50
  type: :development
34
- prerelease: false
35
- version_requirements: *70200311058680
36
- - !ruby/object:Gem::Dependency
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
37
53
  name: rake
38
- requirement: &70200311058220 !ruby/object:Gem::Requirement
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
39
56
  none: false
40
- requirements:
57
+ requirements:
41
58
  - - ~>
42
- - !ruby/object:Gem::Version
59
+ - !ruby/object:Gem::Version
60
+ hash: 63
61
+ segments:
62
+ - 0
63
+ - 9
64
+ - 2
43
65
  version: 0.9.2
44
66
  type: :development
45
- prerelease: false
46
- version_requirements: *70200311058220
47
- - !ruby/object:Gem::Dependency
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
48
69
  name: rdoc
49
- requirement: &70200311057760 !ruby/object:Gem::Requirement
50
- none: false
51
- requirements:
52
- - - ~>
53
- - !ruby/object:Gem::Version
54
- version: 3.9.0
55
- type: :development
56
70
  prerelease: false
57
- version_requirements: *70200311057760
58
- - !ruby/object:Gem::Dependency
59
- name: ruby-debug19
60
- requirement: &70200311057360 !ruby/object:Gem::Requirement
71
+ requirement: &id004 !ruby/object:Gem::Requirement
61
72
  none: false
62
- requirements:
63
- - - ! '>='
64
- - !ruby/object:Gem::Version
65
- version: '0'
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ hash: 17
77
+ segments:
78
+ - 3
79
+ - 11
80
+ version: "3.11"
66
81
  type: :development
67
- prerelease: false
68
- version_requirements: *70200311057360
69
- description: A small fast library for reading dBase, xBase, Clipper and FoxPro database
70
- files.
82
+ version_requirements: *id004
83
+ description: A small fast library for reading dBase, xBase, Clipper and FoxPro database files.
71
84
  email: keithm@infused.org
72
- executables:
85
+ executables:
73
86
  - dbf
74
87
  extensions: []
75
- extra_rdoc_files:
88
+
89
+ extra_rdoc_files:
76
90
  - README.md
77
91
  - CHANGELOG.md
78
92
  - MIT-LICENSE
79
- files:
93
+ files:
80
94
  - CHANGELOG.md
81
95
  - Gemfile
82
96
  - Gemfile.lock
@@ -86,10 +100,14 @@ files:
86
100
  - bin/dbf
87
101
  - docs/supported_types.markdown
88
102
  - lib/dbf/attributes.rb
89
- - lib/dbf/column.rb
103
+ - lib/dbf/column/base.rb
104
+ - lib/dbf/column/dbase.rb
105
+ - lib/dbf/column/foxpro.rb
90
106
  - lib/dbf/encodings.yml
91
- - lib/dbf/foxpro_column.rb
92
- - lib/dbf/memo.rb
107
+ - lib/dbf/memo/base.rb
108
+ - lib/dbf/memo/dbase3.rb
109
+ - lib/dbf/memo/dbase4.rb
110
+ - lib/dbf/memo/foxpro.rb
93
111
  - lib/dbf/record.rb
94
112
  - lib/dbf/table.rb
95
113
  - lib/dbf/util.rb
@@ -111,33 +129,45 @@ files:
111
129
  - spec/fixtures/dbase_8b.dbt
112
130
  - spec/fixtures/dbase_f5.dbf
113
131
  - spec/fixtures/dbase_f5.fpt
132
+ - spec/rvm_ruby_runner.rb
114
133
  - spec/spec_helper.rb
134
+ - dbf.gemspec
115
135
  homepage: http://github.com/infused/dbf
116
136
  licenses: []
137
+
117
138
  post_install_message:
118
- rdoc_options:
139
+ rdoc_options:
119
140
  - --charset=UTF-8
120
- require_paths:
141
+ require_paths:
121
142
  - lib
122
- required_ruby_version: !ruby/object:Gem::Requirement
143
+ required_ruby_version: !ruby/object:Gem::Requirement
123
144
  none: false
124
- requirements:
125
- - - ! '>='
126
- - !ruby/object:Gem::Version
127
- version: '0'
128
- required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ hash: 3
149
+ segments:
150
+ - 0
151
+ version: "0"
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
153
  none: false
130
- requirements:
131
- - - ! '>='
132
- - !ruby/object:Gem::Version
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ hash: 27
158
+ segments:
159
+ - 1
160
+ - 3
161
+ - 0
133
162
  version: 1.3.0
134
163
  requirements: []
164
+
135
165
  rubyforge_project:
136
- rubygems_version: 1.8.10
166
+ rubygems_version: 1.8.12
137
167
  signing_key:
138
168
  specification_version: 3
139
169
  summary: Read xBase files
140
- test_files:
170
+ test_files:
141
171
  - spec/dbf/column_spec.rb
142
172
  - spec/dbf/file_formats_spec.rb
143
173
  - spec/dbf/record_spec.rb
data/lib/dbf/column.rb DELETED
@@ -1,122 +0,0 @@
1
- module DBF
2
- class ColumnLengthError < StandardError; end
3
- class ColumnNameError < StandardError; end
4
-
5
- # DBF::Column stores all the information about a column including its name,
6
- # type, length and number of decimal places (if any)
7
- class Column
8
- attr_reader :name, :type, :length, :decimal
9
-
10
- # Initialize a new DBF::Column
11
- #
12
- # @param [String] name
13
- # @param [String] type
14
- # @param [Fixnum] length
15
- # @param [Fixnum] decimal
16
- def initialize(name, type, length, decimal, version, encoding=nil)
17
- @name, @type, @length, @decimal, @version, @encoding = clean(name), type, length, decimal, version, encoding
18
-
19
- raise ColumnLengthError, "field length must be greater than 0" unless length > 0
20
- raise ColumnNameError, "column name cannot be empty" if @name.length == 0
21
- end
22
-
23
- # Cast value to native type
24
- #
25
- # @param [String] value
26
- # @return [Fixnum, Float, Date, DateTime, Boolean, String]
27
- def type_cast(value)
28
- case type
29
- when 'N' then unpack_number(value)
30
- when 'I' then unpack_unsigned_long(value)
31
- when 'F' then value.to_f
32
- when 'D' then decode_date(value)
33
- when 'T' then decode_datetime(value)
34
- when 'L' then boolean(value)
35
- when 'B' then unpack_binary(value)
36
- else encode_string(value.to_s).strip
37
- end
38
- end
39
-
40
- def memo?
41
- @memo ||= type == 'M'
42
- end
43
-
44
- # Schema definition
45
- #
46
- # @return [String]
47
- def schema_definition
48
- "\"#{underscored_name}\", #{schema_data_type}\n"
49
- end
50
-
51
- def underscored_name
52
- @underscored_name ||= Util.underscore(name)
53
- end
54
-
55
- private
56
-
57
- def decode_date(value) #nodoc
58
- value.gsub!(' ', '0')
59
- value !~ /\S/ ? nil : Date.parse(value)
60
- rescue
61
- nil
62
- end
63
-
64
- def decode_datetime(value) #nodoc
65
- days, milliseconds = value.unpack('l2')
66
- seconds = milliseconds / 1000
67
- DateTime.jd(days, seconds/3600, seconds/60 % 60, seconds % 60) rescue nil
68
- end
69
-
70
- def unpack_number(value) #nodoc
71
- decimal.zero? ? value.to_i : value.to_f
72
- end
73
-
74
- def unpack_unsigned_long(value) #nodoc
75
- value.unpack('V')[0]
76
- end
77
-
78
- def unpack_binary(value) #nodoc
79
- end
80
-
81
- def boolean(value) #nodoc
82
- value.strip =~ /^(y|t)$/i ? true : false
83
- end
84
-
85
- def encode_string(value)
86
- @encoding ? value.force_encoding(@encoding).encode(Encoding.default_external, :undef => :replace, :invalid => :replace) : value
87
- end
88
-
89
- def schema_data_type #nodoc
90
- case type
91
- when "N", "F"
92
- decimal > 0 ? ":float" : ":integer"
93
- when "I"
94
- ":integer"
95
- when "D"
96
- ":date"
97
- when "T"
98
- ":datetime"
99
- when "L"
100
- ":boolean"
101
- when "M"
102
- ":text"
103
- when "B"
104
- if DBF::Table::FOXPRO_VERSIONS.keys.include?(@version)
105
- decimal > 0 ? ":float" : ":integer"
106
- else
107
- ":text"
108
- end
109
- else
110
- ":string, :limit => #{length}"
111
- end
112
- end
113
-
114
- def clean(value) #nodoc
115
- first_null = value.index("\x00")
116
- value = value[0, first_null] if first_null
117
- value.gsub(/[^\x20-\x7E]/, "")
118
- end
119
-
120
- end
121
-
122
- end
@@ -1,7 +0,0 @@
1
- module DBF
2
- class FoxproColumn < Column
3
- def unpack_binary(value) #nodoc
4
- value.unpack('d')[0]
5
- end
6
- end
7
- end
data/lib/dbf/memo.rb DELETED
@@ -1,99 +0,0 @@
1
- module DBF
2
- class Memo
3
- BLOCK_HEADER_SIZE = 8
4
- FPT_HEADER_SIZE = 512
5
- FPT_FORMAT_VERSIONS = DBF::Table::FOXPRO_VERSIONS.keys
6
-
7
- def self.open(filename, version)
8
- self.new File.open(filename, 'rb'), version
9
- end
10
-
11
- def initialize(data, version)
12
- @data, @version = data, version
13
- end
14
-
15
- def format
16
- format_fpt? ? 'fpt' : 'dbt'
17
- end
18
-
19
- def get(start_block)
20
- if start_block > 0
21
- if format_fpt?
22
- build_fpt_memo start_block
23
- else
24
- build_dbt_memo start_block
25
- end
26
- end
27
- end
28
-
29
- def close
30
- @data.close
31
- end
32
-
33
- private
34
-
35
- def format_fpt? #nodoc
36
- FPT_FORMAT_VERSIONS.include?(@version)
37
- end
38
-
39
- def build_fpt_memo(start_block) #nodoc
40
- @data.seek offset(start_block)
41
-
42
- memo_type, memo_size, memo_string = @data.read(block_size).unpack("NNa*")
43
- return nil unless memo_type == 1 && memo_size > 0
44
-
45
- if memo_size > block_content_size
46
- memo_string << @data.read(content_size(memo_size))
47
- else
48
- memo_string = memo_string[0, memo_size]
49
- end
50
- memo_string
51
- end
52
-
53
- def build_dbt_memo(start_block) #nodoc
54
- case @version
55
- when "83" # dbase iii
56
- build_dbt_83_memo(start_block)
57
- when "8b" # dbase iv
58
- build_dbt_8b_memo(start_block)
59
- else
60
- nil
61
- end
62
- end
63
-
64
- def build_dbt_83_memo(start_block) #nodoc
65
- @data.seek offset(start_block)
66
- memo_string = ""
67
- begin
68
- block = @data.read(block_size).gsub(/(\000|\032)/, '')
69
- memo_string << block
70
- end until block.size < block_size
71
- memo_string
72
- end
73
-
74
- def build_dbt_8b_memo(start_block) #nodoc
75
- @data.seek offset(start_block)
76
- @data.read(@data.read(BLOCK_HEADER_SIZE).unpack("x4L").first)
77
- end
78
-
79
- def offset(start_block) #nodoc
80
- start_block * block_size
81
- end
82
-
83
- def content_size(memo_size) #nodoc
84
- (memo_size - block_size) + BLOCK_HEADER_SIZE
85
- end
86
-
87
- def block_content_size #nodoc
88
- @block_content_size ||= block_size - BLOCK_HEADER_SIZE
89
- end
90
-
91
- def block_size #nodoc
92
- @block_size ||= begin
93
- @data.rewind
94
- format_fpt? ? @data.read(FPT_HEADER_SIZE).unpack('x6n').first || 0 : 512
95
- end
96
- end
97
-
98
- end
99
- end