dbf 1.3.0 → 1.5.0
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 +22 -0
- data/Gemfile +1 -4
- data/Gemfile.lock +83 -6
- data/README.md +20 -21
- data/Rakefile +9 -46
- data/bin/dbf +0 -0
- data/lib/dbf.rb +6 -11
- data/lib/dbf/column.rb +38 -74
- data/lib/dbf/memo.rb +88 -0
- data/lib/dbf/record.rb +29 -104
- data/lib/dbf/table.rb +54 -142
- data/lib/dbf/version.rb +1 -1
- data/spec/{unit → dbf}/column_spec.rb +37 -46
- data/spec/dbf/file_formats_spec.rb +178 -0
- data/spec/dbf/record_spec.rb +45 -0
- data/spec/{unit → dbf}/table_spec.rb +17 -104
- data/spec/spec_helper.rb +2 -4
- metadata +39 -27
- data/spec/functional/dbf_shared.rb +0 -54
- data/spec/functional/format_03_spec.rb +0 -23
- data/spec/functional/format_30_spec.rb +0 -23
- data/spec/functional/format_31_spec.rb +0 -23
- data/spec/functional/format_83_spec.rb +0 -23
- data/spec/functional/format_8b_spec.rb +0 -23
- data/spec/functional/format_f5_spec.rb +0 -23
- data/spec/unit/record_spec.rb +0 -111
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
## 1.5.0
|
2
|
+
|
3
|
+
- Significant internal restructuring and performance improvements. Initial
|
4
|
+
testing shows 4x faster performance.
|
5
|
+
|
6
|
+
## 1.3.0
|
7
|
+
|
8
|
+
- Only load what's needed from activesupport 3.0
|
9
|
+
- Updatate fastercsv dependency to 1.5.3
|
10
|
+
- Remove use of 'returning' method
|
11
|
+
- Remove jeweler in favor of manual gemspec creation
|
12
|
+
- Move Table#all_values_match? to Record#match?
|
13
|
+
- Add attr_reader for Record#table
|
14
|
+
- Use method_defined? instead of respond_to? when defining attribute accessors
|
15
|
+
- Move memo file check into get_memo_header_info
|
16
|
+
- Remove unnecessary seek_to_record in Table#each
|
17
|
+
- Add rake console task
|
18
|
+
- New Attribute class
|
19
|
+
- Add a helper method for memo column type
|
20
|
+
- Move constants into the classes where they are used
|
21
|
+
- Use bundler
|
22
|
+
|
1
23
|
## 1.2.9
|
2
24
|
|
3
25
|
- Retain trailing whitespace in memos
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,91 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
dbf (1.3.0)
|
5
|
+
activesupport (= 3.0.1)
|
6
|
+
fastercsv (= 1.5.3)
|
7
|
+
|
1
8
|
GEM
|
2
9
|
remote: http://rubygems.org/
|
3
10
|
specs:
|
4
|
-
|
5
|
-
|
6
|
-
|
11
|
+
Saikuro (1.1.0)
|
12
|
+
activesupport (3.0.1)
|
13
|
+
arrayfields (4.7.4)
|
14
|
+
chronic (0.2.3)
|
15
|
+
hoe (>= 1.2.1)
|
16
|
+
churn (0.0.12)
|
17
|
+
chronic (~> 0.2.3)
|
18
|
+
hirb
|
19
|
+
json_pure
|
20
|
+
main
|
21
|
+
ruby_parser (~> 2.0.4)
|
22
|
+
sexp_processor (~> 3.0.3)
|
23
|
+
colored (1.2)
|
24
|
+
diff-lcs (1.1.2)
|
25
|
+
fastercsv (1.5.3)
|
26
|
+
fattr (2.2.0)
|
27
|
+
flay (1.4.1)
|
28
|
+
ruby_parser (~> 2.0)
|
29
|
+
sexp_processor (~> 3.0)
|
30
|
+
flog (2.5.0)
|
31
|
+
ruby_parser (~> 2.0)
|
32
|
+
sexp_processor (~> 3.0)
|
33
|
+
hirb (0.3.5)
|
34
|
+
hoe (2.6.2)
|
35
|
+
rake (>= 0.8.7)
|
36
|
+
rubyforge (>= 2.0.4)
|
37
|
+
json_pure (1.4.6)
|
38
|
+
main (4.3.0)
|
39
|
+
arrayfields (>= 4.7.4)
|
40
|
+
fattr (>= 2.1.0)
|
41
|
+
metric_fu (2.0.1)
|
42
|
+
Saikuro (>= 1.1.0)
|
43
|
+
activesupport (>= 2.0.0)
|
44
|
+
chronic (~> 0.2.3)
|
45
|
+
churn (>= 0.0.7)
|
46
|
+
flay (>= 1.2.1)
|
47
|
+
flog (>= 2.2.0)
|
48
|
+
rails_best_practices (>= 0.3.16)
|
49
|
+
rcov (>= 0.8.3.3)
|
50
|
+
reek (>= 1.2.6)
|
51
|
+
roodi (>= 2.1.0)
|
52
|
+
progressbar (0.9.0)
|
53
|
+
rails_best_practices (0.5.0)
|
54
|
+
activesupport
|
55
|
+
colored (~> 1.2)
|
56
|
+
progressbar (~> 0.9.0)
|
57
|
+
ruby_parser (~> 2.0.4)
|
58
|
+
rake (0.8.7)
|
59
|
+
rcov (0.9.9)
|
60
|
+
reek (1.2.8)
|
61
|
+
ruby2ruby (~> 1.2)
|
62
|
+
ruby_parser (~> 2.0)
|
63
|
+
sexp_processor (~> 3.0)
|
64
|
+
roodi (2.1.0)
|
65
|
+
ruby_parser
|
66
|
+
rspec (2.1.0)
|
67
|
+
rspec-core (~> 2.1.0)
|
68
|
+
rspec-expectations (~> 2.1.0)
|
69
|
+
rspec-mocks (~> 2.1.0)
|
70
|
+
rspec-core (2.1.0)
|
71
|
+
rspec-expectations (2.1.0)
|
72
|
+
diff-lcs (~> 1.1.2)
|
73
|
+
rspec-mocks (2.1.0)
|
74
|
+
ruby2ruby (1.2.5)
|
75
|
+
ruby_parser (~> 2.0)
|
76
|
+
sexp_processor (~> 3.0)
|
77
|
+
ruby_parser (2.0.5)
|
78
|
+
sexp_processor (~> 3.0)
|
79
|
+
rubyforge (2.0.4)
|
80
|
+
json_pure (>= 1.1.7)
|
81
|
+
sexp_processor (3.0.5)
|
7
82
|
|
8
83
|
PLATFORMS
|
9
84
|
ruby
|
10
85
|
|
11
86
|
DEPENDENCIES
|
12
|
-
activesupport (= 3.0.
|
13
|
-
|
14
|
-
|
87
|
+
activesupport (= 3.0.1)
|
88
|
+
dbf!
|
89
|
+
fastercsv (= 1.5.3)
|
90
|
+
metric_fu (= 2.0.1)
|
91
|
+
rspec (= 2.1.0)
|
data/README.md
CHANGED
@@ -4,14 +4,14 @@ DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro
|
|
4
4
|
database files
|
5
5
|
|
6
6
|
* Project page: <http://github.com/infused/dbf>
|
7
|
-
* API Documentation: <http://
|
7
|
+
* API Documentation: <http://rubydoc.info/github/infused/dbf/frames>
|
8
8
|
* Report bugs: <http://github.com/infused/dbf/issues>
|
9
9
|
* Questions: Email <mailto:keithm@infused.org> and put DBF somewhere in the
|
10
10
|
subject line
|
11
11
|
|
12
12
|
## Compatibility
|
13
13
|
|
14
|
-
DBF
|
14
|
+
DBF works with Ruby 1.8.6, 1.8.7, and 1.9.2
|
15
15
|
|
16
16
|
## Installation
|
17
17
|
|
@@ -23,46 +23,45 @@ Load a DBF file:
|
|
23
23
|
|
24
24
|
require 'dbf'
|
25
25
|
|
26
|
-
|
26
|
+
widgets = DBF::Table.new("widgets.dbf")
|
27
27
|
|
28
28
|
Enumerate all records
|
29
29
|
|
30
|
-
|
31
|
-
puts
|
32
|
-
puts
|
30
|
+
widgets.each do |record|
|
31
|
+
puts widget.name
|
32
|
+
puts widget.email
|
33
33
|
end
|
34
34
|
|
35
|
-
Load a single record using <tt>
|
35
|
+
Load a single record using <tt>find</tt>
|
36
36
|
|
37
|
-
|
38
|
-
table.find(6)
|
37
|
+
widget.find(6)
|
39
38
|
|
40
39
|
Attributes can also be accessed through the attributes hash in original or
|
41
40
|
underscored form or as an accessor method using the underscored name. (Note
|
42
|
-
that
|
41
|
+
that find() will return nil if the requested record has been deleted and not
|
43
42
|
yet pruned from the database)
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
widget.find(4).attributes["SlotNumber"]
|
45
|
+
widget.find(4).attributes["slot_number"]
|
46
|
+
widget.find(4).slot_number
|
48
47
|
|
49
48
|
Search for records using a simple hash format. Multiple search criteria are
|
50
49
|
ANDed. Use the block form if the resulting recordset could be large, otherwise
|
51
50
|
all records will be loaded into memory.
|
52
51
|
|
53
|
-
# find all records with
|
54
|
-
|
52
|
+
# find all records with slot_number equal to s42
|
53
|
+
widgets.find(:all, :slot_number => 's42') do |widget|
|
55
54
|
# the record will be nil if deleted, but not yet pruned from the database
|
56
|
-
if
|
57
|
-
puts
|
55
|
+
if widget
|
56
|
+
puts widget.serial_number
|
58
57
|
end
|
59
58
|
end
|
60
59
|
|
61
|
-
# find the first record with
|
62
|
-
|
60
|
+
# find the first record with slot_number equal to s42
|
61
|
+
widgets.find :first, :slot_number => 's42'
|
63
62
|
|
64
63
|
# find record number 10
|
65
|
-
|
64
|
+
widgets.find(10)
|
66
65
|
|
67
66
|
## Migrating to ActiveRecord
|
68
67
|
|
@@ -100,7 +99,7 @@ A small command-line utility called dbf is installed along with the gem.
|
|
100
99
|
|
101
100
|
The basic dBase data types are generally supported well. Support for the
|
102
101
|
advanced data types in dbase V and FoxPro are still experimental or not
|
103
|
-
supported. If you have insight into how any of unsupported data types are
|
102
|
+
supported. If you have insight into how any of the unsupported data types are
|
104
103
|
implemented, please give me a shout. FoxBase/dBase II files are not supported
|
105
104
|
at this time.
|
106
105
|
|
data/Rakefile
CHANGED
@@ -1,42 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require 'rubygems/specification'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
eval(File.read(file), binding, file)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
begin
|
14
|
-
require 'rake/gempackagetask'
|
15
|
-
rescue LoadError
|
16
|
-
task(:gem) { $stderr.puts '`gem install rake` to package gems' }
|
17
|
-
else
|
18
|
-
Rake::GemPackageTask.new(gemspec) do |pkg|
|
19
|
-
pkg.gem_spec = gemspec
|
20
|
-
end
|
21
|
-
task :gem => :gemspec
|
22
|
-
end
|
23
|
-
|
24
|
-
begin
|
25
|
-
require 'spec/rake/spectask'
|
26
|
-
rescue LoadError
|
27
|
-
raise 'Run `gem install rspec` to be able to run specs'
|
28
|
-
else
|
29
|
-
task :clear_tmp do
|
30
|
-
FileUtils.rm_rf(File.expand_path("../tmp", __FILE__))
|
31
|
-
end
|
32
|
-
|
33
|
-
desc "Run specs"
|
34
|
-
Spec::Rake::SpecTask.new do |t|
|
35
|
-
t.spec_files = FileList['spec/**/*_spec.rb']
|
36
|
-
t.spec_opts = %w(-fs --color)
|
37
|
-
t.warning = true
|
38
|
-
end
|
39
|
-
task :spec
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
RSpec::Core::RakeTask.new :spec do |t|
|
7
|
+
t.rspec_opts = %w(-fs --color)
|
40
8
|
end
|
41
9
|
|
42
10
|
require 'rake'
|
@@ -49,20 +17,15 @@ Rake::RDocTask.new { |rdoc|
|
|
49
17
|
rdoc.rdoc_files.include('README.md', 'docs/supported_types.markdown', 'lib/**/*.rb')
|
50
18
|
}
|
51
19
|
|
52
|
-
desc "install the gem locally"
|
53
|
-
task :install => :package do
|
54
|
-
sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}}
|
55
|
-
end
|
56
|
-
|
57
|
-
desc "validate the gemspec"
|
58
|
-
task :gemspec do
|
59
|
-
gemspec.validate
|
60
|
-
end
|
61
|
-
|
62
|
-
task :package => :gemspec
|
63
20
|
task :default => :spec
|
64
21
|
|
65
22
|
desc "Open an irb session preloaded with this library"
|
66
23
|
task :console do
|
67
24
|
sh "irb -rubygems -I lib -r dbf.rb"
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'metric_fu'
|
28
|
+
MetricFu::Configuration.run do |config|
|
29
|
+
config.rcov[:test_files] = ['spec/**/*_spec.rb']
|
30
|
+
config.rcov[:rcov_opts] << "-Ispec"
|
68
31
|
end
|
data/bin/dbf
CHANGED
File without changes
|
data/lib/dbf.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'date'
|
2
|
-
gem 'activesupport'
|
2
|
+
gem 'activesupport'
|
3
3
|
require 'active_support/core_ext/object'
|
4
4
|
require 'active_support/core_ext/date/conversions'
|
5
5
|
require 'active_support/core_ext/time/conversions'
|
@@ -7,20 +7,15 @@ require 'active_support/core_ext/date_time/conversions'
|
|
7
7
|
require 'active_support/core_ext/string/conversions'
|
8
8
|
require 'active_support/core_ext/module/delegation'
|
9
9
|
require 'active_support/core_ext/string/inflections'
|
10
|
+
require 'active_support/core_ext/enumerable'
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
class Object
|
15
|
-
FCSV = CSV
|
16
|
-
alias_method :FCSV, :CSV
|
17
|
-
end
|
18
|
-
end
|
19
|
-
else
|
20
|
-
require 'fastercsv'
|
12
|
+
require 'csv'
|
13
|
+
if CSV.const_defined? :Reader
|
14
|
+
require 'fastercsv'
|
21
15
|
end
|
22
16
|
|
23
17
|
require 'dbf/attributes'
|
24
18
|
require 'dbf/record'
|
25
19
|
require 'dbf/column'
|
20
|
+
require 'dbf/memo'
|
26
21
|
require 'dbf/table'
|
data/lib/dbf/column.rb
CHANGED
@@ -14,7 +14,7 @@ module DBF
|
|
14
14
|
# @param [Fixnum] length
|
15
15
|
# @param [Fixnum] decimal
|
16
16
|
def initialize(name, type, length, decimal)
|
17
|
-
@name, @type, @length, @decimal =
|
17
|
+
@name, @type, @length, @decimal = clean(name), type, length, decimal
|
18
18
|
|
19
19
|
raise ColumnLengthError, "field length must be greater than 0" unless length > 0
|
20
20
|
raise ColumnNameError, "column name cannot be empty" if @name.length == 0
|
@@ -26,95 +26,67 @@ module DBF
|
|
26
26
|
# @return [Fixnum, Float, Date, DateTime, Boolean, String]
|
27
27
|
def type_cast(value)
|
28
28
|
case type
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
decode_date(value)
|
37
|
-
when 'T' # datetime
|
38
|
-
decode_datetime(value)
|
39
|
-
when 'L' # logical
|
40
|
-
boolean(value)
|
41
|
-
else
|
42
|
-
value.to_s.strip
|
29
|
+
when 'N' then unpack_number(value)
|
30
|
+
when 'I' then unpack_unsigned_long(value)
|
31
|
+
when 'F' then unpack_float(value)
|
32
|
+
when 'D' then decode_date(value)
|
33
|
+
when 'T' then decode_datetime(value)
|
34
|
+
when 'L' then boolean(value)
|
35
|
+
else value.to_s.strip
|
43
36
|
end
|
44
37
|
end
|
45
38
|
|
46
|
-
|
39
|
+
def memo?
|
40
|
+
@memo ||= type == 'M'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Schema definition
|
47
44
|
#
|
48
|
-
# @
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
45
|
+
# @return [String]
|
46
|
+
def schema_definition
|
47
|
+
"\"#{name.underscore}\", #{schema_data_type}\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
def underscored_name
|
51
|
+
@underscored_name ||= name.underscore
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def decode_date(value) #nodoc
|
57
|
+
value.gsub!(' ', '0')
|
58
|
+
value.blank? ? nil : value.to_date
|
53
59
|
rescue
|
54
60
|
nil
|
55
61
|
end
|
56
62
|
|
57
|
-
|
58
|
-
#
|
59
|
-
# @param [String] value
|
60
|
-
# @return [DateTime]
|
61
|
-
def decode_datetime(value)
|
63
|
+
def decode_datetime(value) #nodoc
|
62
64
|
days, milliseconds = value.unpack('l2')
|
63
65
|
seconds = milliseconds / 1000
|
64
66
|
DateTime.jd(days, seconds/3600, seconds/60 % 60, seconds % 60) rescue nil
|
65
67
|
end
|
66
68
|
|
67
|
-
|
68
|
-
#
|
69
|
-
# @param [String] value
|
70
|
-
# @return [Fixnum, Float]
|
71
|
-
def unpack_number(value)
|
69
|
+
def unpack_number(value) #nodoc
|
72
70
|
decimal.zero? ? unpack_integer(value) : value.to_f
|
73
71
|
end
|
74
72
|
|
75
|
-
|
76
|
-
#
|
77
|
-
# @param [String] value
|
78
|
-
# @return [Float]
|
79
|
-
def unpack_float(value)
|
73
|
+
def unpack_float(value) #nodoc
|
80
74
|
value.to_f
|
81
75
|
end
|
82
76
|
|
83
|
-
|
84
|
-
#
|
85
|
-
# @param [String] value
|
86
|
-
# @return [Fixnum]
|
87
|
-
def unpack_integer(value)
|
77
|
+
def unpack_integer(value) #nodoc
|
88
78
|
value.to_i
|
89
79
|
end
|
90
80
|
|
91
|
-
|
92
|
-
#
|
93
|
-
# @param [String] value
|
94
|
-
# @return [Fixnum]
|
95
|
-
def unpack_unsigned_long(value)
|
81
|
+
def unpack_unsigned_long(value) #nodoc
|
96
82
|
value.unpack('V')[0]
|
97
83
|
end
|
98
84
|
|
99
|
-
|
100
|
-
#
|
101
|
-
# @param [String] value
|
102
|
-
# @return [Boolean]
|
103
|
-
def boolean(value)
|
85
|
+
def boolean(value) #nodoc
|
104
86
|
value.strip =~ /^(y|t)$/i ? true : false
|
105
87
|
end
|
106
88
|
|
107
|
-
|
108
|
-
#
|
109
|
-
# @return [String]
|
110
|
-
def schema_definition
|
111
|
-
"\"#{name.underscore}\", #{schema_data_type}\n"
|
112
|
-
end
|
113
|
-
|
114
|
-
# Column type for schema definition
|
115
|
-
#
|
116
|
-
# @return [String]
|
117
|
-
def schema_data_type
|
89
|
+
def schema_data_type #nodoc
|
118
90
|
case type
|
119
91
|
when "N", "F"
|
120
92
|
decimal > 0 ? ":float" : ":integer"
|
@@ -133,20 +105,12 @@ module DBF
|
|
133
105
|
end
|
134
106
|
end
|
135
107
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
def strip_non_ascii_chars(s)
|
141
|
-
# truncate the string at the first null character
|
142
|
-
s = s[0, s.index("\x00")] if s.index("\x00")
|
143
|
-
|
144
|
-
s.gsub(/[^\x20-\x7E]/,"")
|
108
|
+
def clean(s) #nodoc
|
109
|
+
first_null = s.index("\x00")
|
110
|
+
s = s[0, first_null] if first_null
|
111
|
+
s.gsub(/[^\x20-\x7E]/, "")
|
145
112
|
end
|
146
113
|
|
147
|
-
def memo?
|
148
|
-
type == 'M'
|
149
|
-
end
|
150
114
|
end
|
151
115
|
|
152
116
|
end
|