dbf 1.0.7 → 1.0.8

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.
@@ -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.