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 +4 -0
- data/Gemfile.lock +7 -18
- data/README.md +7 -1
- data/Rakefile +33 -2
- data/dbf.gemspec +39 -0
- data/lib/dbf.rb +7 -3
- data/lib/dbf/column/base.rb +122 -0
- data/lib/dbf/column/dbase.rb +7 -0
- data/lib/dbf/column/foxpro.rb +9 -0
- data/lib/dbf/memo/base.rb +43 -0
- data/lib/dbf/memo/dbase3.rb +15 -0
- data/lib/dbf/memo/dbase4.rb +10 -0
- data/lib/dbf/memo/foxpro.rb +30 -0
- data/lib/dbf/table.rb +25 -9
- data/lib/dbf/version.rb +1 -1
- data/spec/dbf/column_spec.rb +38 -29
- data/spec/dbf/file_formats_spec.rb +2 -2
- data/spec/dbf/table_spec.rb +15 -2
- data/spec/rvm_ruby_runner.rb +13 -0
- data/spec/spec_helper.rb +7 -0
- metadata +93 -63
- data/lib/dbf/column.rb +0 -122
- data/lib/dbf/foxpro_column.rb +0 -7
- data/lib/dbf/memo.rb +0 -99
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dbf (1.7.
|
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
|
-
|
15
|
-
|
12
|
+
json (1.6.3)
|
13
|
+
json (1.6.3-java)
|
16
14
|
rake (0.9.2.2)
|
17
|
-
rdoc (3.
|
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.
|
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
|
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
|
+
[](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(
|
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/
|
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,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,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
|
207
|
-
|
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
|
-
|
239
|
+
memo_class.new(memo, version)
|
224
240
|
elsif memo
|
225
|
-
|
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? ?
|
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
data/spec/dbf/column_spec.rb
CHANGED
@@ -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::
|
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::
|
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::
|
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
|
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
|
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
|
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
|
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.
|
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::
|
57
|
+
@table.columns.all? {|column| column.should be_an_instance_of(DBF::Column::Foxpro)}
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
data/spec/dbf/table_spec.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
date: 2011-12-28 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
15
21
|
name: fastercsv
|
16
|
-
|
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
|
-
|
24
|
-
|
25
|
-
- !ruby/object:Gem::Dependency
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
26
37
|
name: rspec
|
27
|
-
|
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
|
-
|
35
|
-
|
36
|
-
- !ruby/object:Gem::Dependency
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
37
53
|
name: rake
|
38
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
68
|
-
|
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
|
-
|
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/
|
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
|
-
|
128
|
-
|
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.
|
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
|
data/lib/dbf/foxpro_column.rb
DELETED
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
|