dbf 1.7.1 → 1.7.2

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