dbf 1.0.7 → 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,10 @@
1
+ == 1.0.8
2
+
3
+ * Truncate column names on NULL
4
+ * Fix schema dump for date and datetime columns
5
+ * Replace internal helpers with ActiveSupport
6
+ * Always underscore attribute names
7
+
1
8
  == 1.0.7
2
9
 
3
10
  * Remove support for original column names. All columns names are now downcased/underscored.
data/README.txt CHANGED
@@ -1,8 +1,8 @@
1
1
  = DBF
2
2
 
3
- DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro database files. It is written completely in Ruby and has no external dependencies.
3
+ DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro database files
4
4
 
5
- Copyright (c) 2006-2008 Keith Morrison <keithm@infused.org, www.infused.org>
5
+ Copyright (c) 2006-2009 Keith Morrison <keithm@infused.org, www.infused.org>
6
6
 
7
7
  * Official project page: http://rubyforge.org/projects/dbf
8
8
  * API Documentation: http://dbf.rubyforge.org/docs
@@ -91,7 +91,7 @@ A small command-line utility called dbf is installed along with the gem.
91
91
 
92
92
  (The MIT Licence)
93
93
 
94
- Copyright (c) 2006-2008 Keith Morrison <keithm@infused.org>
94
+ Copyright (c) 2006-2009 Keith Morrison <keithm@infused.org, www.infused.org>
95
95
 
96
96
  Permission is hereby granted, free of charge, to any person
97
97
  obtaining a copy of this software and associated documentation
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'hoe'
2
2
  require 'spec/rake/spectask'
3
3
 
4
4
  PKG_NAME = "dbf"
5
- PKG_VERSION = "1.0.7"
5
+ PKG_VERSION = "1.0.8"
6
6
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
7
7
 
8
8
  Hoe.new PKG_NAME, PKG_VERSION do |p|
data/bin/dbf CHANGED
@@ -18,22 +18,22 @@ else
18
18
 
19
19
  # create an ActiveRecord::Schema
20
20
  if $a
21
- reader = DBF::Table.new filename
22
- puts reader.schema
21
+ table = DBF::Table.new filename
22
+ puts table.schema
23
23
  end
24
24
 
25
25
  if $s
26
- reader = DBF::Table.new filename
26
+ table = DBF::Table.new filename
27
27
  puts
28
28
  puts "Database: #{filename}"
29
- puts "Type: (#{reader.version}) #{reader.version_description}"
30
- puts "Memo Type: #{reader.memo_file_format}" if reader.has_memo_file?
31
- puts "Records: #{reader.record_count}"
29
+ puts "Type: (#{table.version}) #{table.version_description}"
30
+ puts "Memo Type: #{table.memo_file_format}" if table.has_memo_file?
31
+ puts "Records: #{table.record_count}"
32
32
 
33
33
  puts "\nFields:"
34
34
  puts "Name Type Length Decimal"
35
35
  puts "-" * 78
36
- reader.columns.each do |f|
36
+ table.columns.each do |f|
37
37
  puts "%-16s %-10s %-10s %-10s" % [f.name, f.type, f.length, f.decimal]
38
38
  end
39
39
  end
@@ -1,38 +1,37 @@
1
- (in /Users/keithm/projects/dbf)
2
1
  Gem::Specification.new do |s|
3
2
  s.name = %q{dbf}
4
- s.version = "1.0.7"
3
+ s.version = "1.0.8"
5
4
 
6
5
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
7
6
  s.authors = ["Keith Morrison"]
8
- s.date = %q{2008-09-02}
7
+ s.date = %q{2009-01-02}
9
8
  s.default_executable = %q{dbf}
10
- s.description = %q{DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro database files. It is written completely in Ruby and has no external dependencies. Copyright (c) 2006-2008 Keith Morrison <keithm@infused.org, www.infused.org> * Official project page: http://rubyforge.org/projects/dbf * API Documentation: http://dbf.rubyforge.org/docs * To report bugs: http://www.rubyforge.org/tracker/?group_id=2009 * Questions: Email keithm@infused.org and put DBF somewhere in the subject line}
9
+ s.description = %q{DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro database files Copyright (c) 2006-2009 Keith Morrison <keithm@infused.org, www.infused.org> * Official project page: http://rubyforge.org/projects/dbf * API Documentation: http://dbf.rubyforge.org/docs * To report bugs: http://www.rubyforge.org/tracker/?group_id=2009 * Questions: Email keithm@infused.org and put DBF somewhere in the subject line}
11
10
  s.email = %q{keithm@infused.org}
12
11
  s.executables = ["dbf"]
13
- s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt", "spec/fixtures/dbase_83_schema.txt"]
12
+ s.extra_rdoc_files = ["History.txt", "README.txt"]
14
13
  s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "bin/dbf", "dbf.gemspec", "lib/dbf.rb", "lib/dbf/column.rb", "lib/dbf/globals.rb", "lib/dbf/record.rb", "lib/dbf/table.rb", "spec/fixtures/dbase_03.dbf", "spec/fixtures/dbase_30.dbf", "spec/fixtures/dbase_30.fpt", "spec/fixtures/dbase_83.dbf", "spec/fixtures/dbase_83.dbt", "spec/fixtures/dbase_83_schema.txt", "spec/fixtures/dbase_8b.dbf", "spec/fixtures/dbase_8b.dbt", "spec/fixtures/dbase_f5.dbf", "spec/fixtures/dbase_f5.fpt", "spec/functional/dbf_shared.rb", "spec/functional/format_03_spec.rb", "spec/functional/format_30_spec.rb", "spec/functional/format_83_spec.rb", "spec/functional/format_8b_spec.rb", "spec/functional/format_f5_spec.rb", "spec/spec_helper.rb", "spec/unit/column_spec.rb", "spec/unit/record_spec.rb", "spec/unit/table_spec.rb"]
15
14
  s.has_rdoc = true
16
15
  s.homepage = %q{http://github.com/infused/dm-dbf/tree/master}
17
16
  s.rdoc_options = ["--main", "README.txt"]
18
17
  s.require_paths = ["lib"]
19
18
  s.rubyforge_project = %q{dbf}
20
- s.rubygems_version = %q{1.2.0}
19
+ s.rubygems_version = %q{1.3.1}
21
20
  s.summary = %q{A small fast library for reading dBase, xBase, Clipper and FoxPro database files.}
22
21
 
23
22
  if s.respond_to? :specification_version then
24
23
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
24
  s.specification_version = 2
26
25
 
27
- if current_version >= 3 then
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
27
  s.add_runtime_dependency(%q<activesupport>, [">= 2.1.0"])
29
- s.add_development_dependency(%q<hoe>, [">= 1.7.0"])
28
+ s.add_development_dependency(%q<hoe>, [">= 1.8.2"])
30
29
  else
31
30
  s.add_dependency(%q<activesupport>, [">= 2.1.0"])
32
- s.add_dependency(%q<hoe>, [">= 1.7.0"])
31
+ s.add_dependency(%q<hoe>, [">= 1.8.2"])
33
32
  end
34
33
  else
35
34
  s.add_dependency(%q<activesupport>, [">= 2.1.0"])
36
- s.add_dependency(%q<hoe>, [">= 1.7.0"])
35
+ s.add_dependency(%q<hoe>, [">= 1.8.2"])
37
36
  end
38
37
  end
@@ -1,14 +1,44 @@
1
1
  module DBF
2
2
  class ColumnLengthError < DBFError; end
3
+ class ColumnNameError < DBFError; end
3
4
 
4
5
  class Column
5
- include Helpers
6
-
7
6
  attr_reader :name, :type, :length, :decimal
8
-
7
+
9
8
  def initialize(name, type, length, decimal)
10
- raise ColumnLengthError, "field length must be greater than 0" unless length > 0
11
9
  @name, @type, @length, @decimal = strip_non_ascii_chars(name), type, length, decimal
10
+
11
+ raise ColumnLengthError, "field length must be greater than 0" unless length > 0
12
+ raise ColumnNameError, "column name cannot be empty" if @name.length == 0
13
+ end
14
+
15
+ def type_cast(value)
16
+ value = value.is_a?(Array) ? value.first : value
17
+
18
+ case type
19
+ when 'N' # number
20
+ decimal.zero? ? unpack_integer(value) : value.to_f
21
+ when 'D' # date
22
+ value.to_date unless value.blank?
23
+ when 'L' # logical
24
+ value.strip =~ /^(y|t)$/i ? true : false
25
+ when 'I' # integer
26
+ unpack_integer(value)
27
+ when 'T' # datetime
28
+ decode_datetime(value)
29
+ else
30
+ value.to_s.strip
31
+ end
32
+ end
33
+
34
+ def decode_datetime(value)
35
+ days, milliseconds = value.unpack('l2')
36
+ seconds = milliseconds / 1000
37
+ DateTime.jd(days, seconds/3600, seconds/60 % 60, seconds % 60)
38
+ end
39
+
40
+ def unpack_integer(value)
41
+ value.unpack('v').first.to_i
12
42
  end
13
43
 
14
44
  def schema_definition
@@ -19,7 +49,11 @@ module DBF
19
49
  else
20
50
  ":integer"
21
51
  end
52
+ when "I" # integer
53
+ ":integer"
22
54
  when "D" # date
55
+ ":date"
56
+ when "T" # datetime
23
57
  ":datetime"
24
58
  when "L" # boolean
25
59
  ":boolean"
@@ -32,15 +66,13 @@ module DBF
32
66
  "\"#{name.underscore}\", #{data_type}\n"
33
67
  end
34
68
 
35
- private
36
-
69
+ # strip all non-ascii and non-printable characters
37
70
  def strip_non_ascii_chars(s)
38
- clean = ''
39
- s.each_byte do |char|
40
- clean << char if char > 31 && char < 128
41
- end
42
- clean
71
+ # truncate the string at the first null character
72
+ s = s[0, s.index("\x00")] if s.index("\x00")
73
+
74
+ s.gsub(/[^\x20-\x7E]/,"")
43
75
  end
44
76
  end
45
77
 
46
- end
78
+ end
@@ -23,14 +23,5 @@ module DBF
23
23
  MS_PER_HOUR = MS_PER_MINUTE * 60
24
24
 
25
25
  class DBFError < StandardError; end
26
-
27
- module Helpers
28
- # def underscore(camel_cased_word)
29
- # camel_cased_word.to_s.gsub(/::/, '/').
30
- # gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
31
- # gsub(/([a-z\d])([A-Z])/,'\1_\2').
32
- # tr("-", "_").
33
- # downcase
34
- # end
35
- end
26
+
36
27
  end
@@ -1,7 +1,5 @@
1
1
  module DBF
2
2
  class Record
3
- include Helpers
4
-
5
3
  attr_reader :attributes
6
4
 
7
5
  def initialize(table)
@@ -29,31 +27,16 @@ module DBF
29
27
 
30
28
  def initialize_values(columns)
31
29
  @attributes = columns.inject({}) do |hash, column|
32
- hash[column.name.underscore] = typecast_column(column)
30
+ if column.type == 'M'
31
+ starting_block = unpack_string(column).to_i
32
+ hash[column.name.underscore] = read_memo(starting_block)
33
+ else
34
+ value = unpack_column(column)
35
+ hash[column.name.underscore] = column.type_cast(value)
36
+ end
33
37
  hash
34
38
  end
35
39
  end
36
-
37
- def typecast_column(column)
38
- case column.type
39
- when 'N' # number
40
- column.decimal.zero? ? unpack_string(column).to_i : unpack_string(column).to_f
41
- when 'D' # date
42
- raw = unpack_string(column).strip
43
- raw.to_time unless raw.blank?
44
- when 'M' # memo
45
- starting_block = unpack_string(column).to_i
46
- read_memo(starting_block)
47
- when 'L' # logical
48
- unpack_string(column) =~ /^(y|t)$/i ? true : false
49
- when 'I' # integer
50
- unpack_integer(column)
51
- when 'T' # datetime
52
- unpack_datetime(column)
53
- else
54
- unpack_string(column).strip
55
- end
56
- end
57
40
 
58
41
  def unpack_column(column)
59
42
  @data.read(column.length).to_s.unpack("a#{column.length}")
@@ -62,18 +45,6 @@ module DBF
62
45
  def unpack_string(column)
63
46
  unpack_column(column).to_s
64
47
  end
65
-
66
- def unpack_integer(column)
67
- @data.read(column.length).unpack("v").first
68
- end
69
-
70
- def unpack_datetime(column)
71
- days, milliseconds = @data.read(column.length).unpack('l2')
72
- hours = (milliseconds / MS_PER_HOUR).to_i
73
- minutes = ((milliseconds - (hours * MS_PER_HOUR)) / MS_PER_MINUTE).to_i
74
- seconds = ((milliseconds - (hours * MS_PER_HOUR) - (minutes * MS_PER_MINUTE)) / MS_PER_SECOND).to_i
75
- DateTime.jd(days, hours, minutes, seconds)
76
- end
77
48
 
78
49
  def read_memo(start_block)
79
50
  return nil if !@table.has_memo_file? || start_block < 1
@@ -13,9 +13,9 @@ module DBF
13
13
  attr_reader :data # DBF file handle
14
14
  attr_reader :memo # Memo file handle
15
15
 
16
- # Initializes a new DBF::Reader
16
+ # Initializes a new DBF::Table
17
17
  # Example:
18
- # reader = DBF::Reader.new 'data.dbf'
18
+ # table = DBF::Table.new 'data.dbf'
19
19
  def initialize(filename, options = {})
20
20
  @data = File.open(filename, 'rb')
21
21
  @memo = open_memo(filename)
@@ -78,16 +78,16 @@ module DBF
78
78
  # Find records using a simple ActiveRecord-like syntax.
79
79
  #
80
80
  # Examples:
81
- # reader = DBF::Reader.new 'mydata.dbf'
81
+ # table = DBF::Table.new 'mydata.dbf'
82
82
  #
83
83
  # # Find record number 5
84
- # reader.find(5)
84
+ # table.find(5)
85
85
  #
86
86
  # # Find all records for Keith Morrison
87
- # reader.find :all, :first_name => "Keith", :last_name => "Morrison"
87
+ # table.find :all, :first_name => "Keith", :last_name => "Morrison"
88
88
  #
89
89
  # # Find first record
90
- # reader.find :first, :first_name => "Keith"
90
+ # table.find :first, :first_name => "Keith"
91
91
  #
92
92
  # The <b>command</b> can be an id, :all, or :first.
93
93
  # <b>options</b> is optional and, if specified, should be a hash where the keys correspond
@@ -24,9 +24,57 @@ describe DBF::Column do
24
24
  end
25
25
 
26
26
  it "should raise an error if length is greater than 0" do
27
- lambda { column = DBF::Column.new "ColumnName", "N", -1, 0 }.should raise_error(DBF::ColumnLengthError)
27
+ lambda { DBF::Column.new "ColumnName", "N", -1, 0 }.should raise_error(DBF::ColumnLengthError)
28
+ end
29
+
30
+ it "should raise error on emtpy column names" do
31
+ lambda { DBF::Column.new "\xFF\xFC", "N", 1, 0 }.should raise_error(DBF::ColumnNameError)
32
+ end
33
+
34
+ end
35
+
36
+ context "#type_cast" do
37
+ it "should cast numbers with decimals to Float" do
38
+ value = "13.5"
39
+ column = DBF::Column.new "ColumnName", "N", 2, 1
40
+ column.type_cast(value).should == 13.5
41
+ end
42
+
43
+ it "should cast numbers with no decimals to Integer" do
44
+ value = "135"
45
+ column = DBF::Column.new "ColumnName", "N", 3, 0
46
+ column.type_cast(value).should == 13105
47
+ end
48
+
49
+ it "should cast :integer to Integer" do
50
+ value = "135"
51
+ column = DBF::Column.new "ColumnName", "I", 3, 0
52
+ column.type_cast(value).should == 13105
53
+ end
54
+
55
+ it "should cast boolean to True" do
56
+ value = "y"
57
+ column = DBF::Column.new "ColumnName", "L", 1, 0
58
+ column.type_cast(value).should == true
59
+ end
60
+
61
+ it "should cast boolean to False" do
62
+ value = "n"
63
+ column = DBF::Column.new "ColumnName", "L", 1, 0
64
+ column.type_cast(value).should == false
65
+ end
66
+
67
+ it "should cast :datetime columns to DateTime" do
68
+ value = "Nl%\000\300Z\252\003"
69
+ column = DBF::Column.new "ColumnName", "T", 16, 0
70
+ column.type_cast(value).should == "2002-10-10T17:04:56+00:00"
71
+ end
72
+
73
+ it "should cast :date columns to Date" do
74
+ value = "20050712"
75
+ column = DBF::Column.new "ColumnName", "D", 8, 0
76
+ column.type_cast(value).should == Date.new(2005,7,12)
28
77
  end
29
-
30
78
  end
31
79
 
32
80
  context "#schema_definition" do
@@ -40,8 +88,13 @@ describe DBF::Column do
40
88
  column.schema_definition.should == "\"column_name\", :float\n"
41
89
  end
42
90
 
43
- it "should define a datetime column if type is (D)ate" do
91
+ it "should define a date column if type is (D)ate" do
44
92
  column = DBF::Column.new "ColumnName", "D", 8, 0
93
+ column.schema_definition.should == "\"column_name\", :date\n"
94
+ end
95
+
96
+ it "should define a datetime column if type is (D)ate" do
97
+ column = DBF::Column.new "ColumnName", "T", 16, 0
45
98
  column.schema_definition.should == "\"column_name\", :datetime\n"
46
99
  end
47
100
 
@@ -62,10 +115,17 @@ describe DBF::Column do
62
115
  end
63
116
 
64
117
  context "#strip_non_ascii_chars" do
65
- it "should strip characters below decimal 32 and above decimal 128" do
66
- column = DBF::Column.new "ColumnName", "N", 1, 0
67
- column.send(:strip_non_ascii_chars, "--\x1F-\x68\x65\x6C\x6C\x6F world-\x80--").should == "---hello world---"
118
+ before do
119
+ @column = DBF::Column.new "ColumnName", "N", 1, 0
120
+ end
121
+
122
+ it "should strip characters below decimal 32 and above decimal 127" do
123
+ @column.strip_non_ascii_chars("--\x1F-\x68\x65\x6C\x6C\x6F world-\x80--").should == "---hello world---"
124
+ end
125
+
126
+ it "should truncate characters with decimal 0" do
127
+ @column.strip_non_ascii_chars("--\x1F-\x68\x65\x6C\x6C\x6F \x00 world-\x80--").should == "---hello "
68
128
  end
69
129
  end
70
130
 
71
- end
131
+ end
@@ -44,19 +44,19 @@ describe DBF::Record do
44
44
  table.records.all? {|record| record.attributes["webinclude"].should satisfy {|v| v == true || v == false}}
45
45
  end
46
46
 
47
- it "should typecast datetime columns to DateTime" do
48
- record = example_record("Nl%\000\300Z\252\003")
49
- column = mock('column', :length => 8)
50
-
51
- record.instance_eval {unpack_datetime(column)}.to_s.should == "2002-10-10T17:04:56+00:00"
52
- end
53
-
54
- it "should typecast integers to Fixnum" do
55
- record = example_record("\017\020\000\000")
56
- column = mock('column', :length => 4)
57
-
58
- record.instance_eval {unpack_integer(column)}.should == 4111
59
- end
47
+ # it "should typecast datetime columns to DateTime" do
48
+ # record = example_record("Nl%\000\300Z\252\003")
49
+ # column = mock('column', :length => 8)
50
+ #
51
+ # record.instance_eval {unpack_datetime(column)}.to_s.should == "2002-10-10T17:04:56+00:00"
52
+ # end
53
+ #
54
+ # it "should typecast integers to Fixnum" do
55
+ # record = example_record("\017\020\000\000")
56
+ # column = mock('column', :length => 4)
57
+ #
58
+ # record.instance_eval {unpack_integer(column)}.should == 4111
59
+ # end
60
60
  end
61
61
 
62
62
  describe '#memo_block_content_size' do
@@ -95,30 +95,5 @@ describe DBF::Record do
95
95
  record.send(:read_memo, 5).should be_nil
96
96
  end
97
97
  end
98
-
99
- describe "#typecase_column" do
100
- before do
101
- @table = mock_table
102
- @column = mock('column')
103
- @column.stub!(:name).and_return('created')
104
- @column.stub!(:length).and_return(8)
105
- @column.stub!(:type).and_return('D')
106
- @table.stub!(:columns).and_return([@column])
107
- @record = DBF::Record.new(@table)
108
- end
109
-
110
- describe 'when column is type D' do
111
- it 'should return Time' do
112
- @record.stub!(:unpack_string).and_return('20080606')
113
- @record.send(:typecast_column, @column).should == Time.gm(2008, 6, 6)
114
- end
115
-
116
- it 'should return Date if Time is out of range' do
117
- @record.stub!(:unpack_string).and_return('19440606')
118
- @record.send(:typecast_column, @column).should == Date.new(1944, 6, 6)
119
- end
120
- end
121
-
122
- end
123
98
 
124
99
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Morrison
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-09-02 00:00:00 -07:00
12
+ date: 2009-01-02 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -30,9 +30,9 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.7.0
33
+ version: 1.8.2
34
34
  version:
35
- description: "DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro database files. It is written completely in Ruby and has no external dependencies. Copyright (c) 2006-2008 Keith Morrison <keithm@infused.org, www.infused.org> * Official project page: http://rubyforge.org/projects/dbf * API Documentation: http://dbf.rubyforge.org/docs * To report bugs: http://www.rubyforge.org/tracker/?group_id=2009 * Questions: Email keithm@infused.org and put DBF somewhere in the subject line"
35
+ description: "DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro database files Copyright (c) 2006-2009 Keith Morrison <keithm@infused.org, www.infused.org> * Official project page: http://rubyforge.org/projects/dbf * API Documentation: http://dbf.rubyforge.org/docs * To report bugs: http://www.rubyforge.org/tracker/?group_id=2009 * Questions: Email keithm@infused.org and put DBF somewhere in the subject line"
36
36
  email: keithm@infused.org
37
37
  executables:
38
38
  - dbf
@@ -98,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
98
  requirements: []
99
99
 
100
100
  rubyforge_project: dbf
101
- rubygems_version: 1.2.0
101
+ rubygems_version: 1.3.1
102
102
  signing_key:
103
103
  specification_version: 2
104
104
  summary: A small fast library for reading dBase, xBase, Clipper and FoxPro database files.