dbf 3.1.0 → 3.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aecf544df6670b726e4edfbf08511819e9eb67d3
4
- data.tar.gz: 7b5e97b5e6102e13648ed1542066f47d78cebba3
3
+ metadata.gz: a8b06626d3eb46fa746bd21d8b359c099dec90b2
4
+ data.tar.gz: e1da4bd35cc876d257552581d1d41079d55fc15c
5
5
  SHA512:
6
- metadata.gz: 5dd35b97f6f334523543f0885a893c37eb7edfba91ffe529c769bc38f51a7ffed55edc4eb50b6d74e587a56d5c35055d1188ac97e024ceb8b24e14527cb21e29
7
- data.tar.gz: a4faecdc9176581351003c30ef8651d8a7fbe6031b8cc85989f20677a3eb95f6d76e919eb7d4f017d129abff23670472ac71c0371763ae3555936225f5344196
6
+ metadata.gz: 1496225ad7fbbc044287627d2a13196e70614d4adbd7467911b51bd4860ce6e35f86c86713c5ec05dbed19a0c956bad80e2906ea8c6541ffd4f6a26764e73304
7
+ data.tar.gz: 23f28160bb106378de8e7ffe409ab1db78d31d6c8f380a5a2e21e05f820b48b86030dba346e6d5c461cb292cfbd640c7562ec5a653e29055ea3822166bf97593
@@ -1,3 +1,6 @@
1
+ # 3.1.1
2
+ - Use Date.strptime to parse date fields
3
+
1
4
  # 3.1.0
2
5
  - Use :binary for binary fields in ActiveRecord schemas
3
6
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dbf (3.0.8)
4
+ dbf (3.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,6 +10,7 @@ GEM
10
10
  coderay (1.1.0)
11
11
  diff-lcs (1.2.5)
12
12
  ffi (1.9.10)
13
+ ffi (1.9.10-java)
13
14
  formatador (0.2.5)
14
15
  guard (2.13.0)
15
16
  formatador (>= 0.2.4)
@@ -25,7 +26,7 @@ GEM
25
26
  guard (~> 2.1)
26
27
  guard-compat (~> 1.1)
27
28
  rspec (>= 2.99.0, < 4.0)
28
- listen (3.0.3)
29
+ listen (3.0.5)
29
30
  rb-fsevent (>= 0.9.3)
30
31
  rb-inotify (>= 0.9)
31
32
  lumberjack (1.0.9)
@@ -34,11 +35,16 @@ GEM
34
35
  notiffany (0.0.8)
35
36
  nenv (~> 0.1)
36
37
  shellany (~> 0.0)
37
- pry (0.10.2)
38
+ pry (0.10.3)
38
39
  coderay (~> 1.1.0)
39
40
  method_source (~> 0.8.1)
40
41
  slop (~> 3.4)
41
- rake (10.4.2)
42
+ pry (0.10.3-java)
43
+ coderay (~> 1.1.0)
44
+ method_source (~> 0.8.1)
45
+ slop (~> 3.4)
46
+ spoon (~> 0.0)
47
+ rake (11.2.2)
42
48
  rb-fsevent (0.9.6)
43
49
  rb-inotify (0.9.5)
44
50
  ffi (>= 0.5.0)
@@ -57,9 +63,12 @@ GEM
57
63
  rspec-support (3.4.0)
58
64
  shellany (0.0.1)
59
65
  slop (3.6.0)
66
+ spoon (0.0.4)
67
+ ffi
60
68
  thor (0.19.1)
61
69
 
62
70
  PLATFORMS
71
+ java
63
72
  ruby
64
73
 
65
74
  DEPENDENCIES
@@ -71,4 +80,4 @@ DEPENDENCIES
71
80
  rspec
72
81
 
73
82
  BUNDLED WITH
74
- 1.11.2
83
+ 1.13.7
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2016 Keith Morrison <keithm@infused.org>
1
+ Copyright (c) 2006-2017 Keith Morrison <keithm@infused.org>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -22,8 +22,8 @@ NOTE: beginning with version 3 we have dropped support for Ruby 1.8 and 1.9. If
22
22
 
23
23
  DBF is tested to work with the following versions of Ruby:
24
24
 
25
- * MRI Ruby 2.0.x, 2.1.x, 2.2.x, 2.3.x
26
- * JRuby head
25
+ * MRI Ruby 2.0.x, 2.1.x, 2.2.x, 2.3.x, 2.4.x
26
+ * JRuby 1.7.x
27
27
 
28
28
  ## Installation
29
29
 
@@ -79,21 +79,17 @@ widget = widgets.find(6)
79
79
  Note that find() will return nil if the requested record has been deleted
80
80
  and not yet pruned from the database.
81
81
 
82
- The value for a attribute can be accessed via element reference in one of three
83
- ways
82
+ The value for an attribute can be accessed via element reference in several
83
+ ways.
84
84
 
85
85
  ```ruby
86
+ widget.slot_number # underscored field name as method
87
+
86
88
  widget["SlotNumber"] # original field name in dbf file
87
89
  widget['slot_number'] # underscored field name string
88
90
  widget[:slot_number] # underscored field name symbol
89
91
  ```
90
92
 
91
- Attributes can also be accessed as method using the underscored field name
92
-
93
- ```ruby
94
- widget.slot_number
95
- ```
96
-
97
93
  Get a hash of all attributes. The keys are the original column names.
98
94
 
99
95
  ```ruby
@@ -276,7 +272,7 @@ for a full list of supported column types.
276
272
 
277
273
  ## License
278
274
 
279
- Copyright (c) 2006-2016 Keith Morrison <<keithm@infused.org>>
275
+ Copyright (c) 2006-2017 Keith Morrison <<keithm@infused.org>>
280
276
 
281
277
  Permission is hereby granted, free of charge, to any person
282
278
  obtaining a copy of this software and associated documentation
@@ -1,10 +1,10 @@
1
1
  module DBF
2
2
  class Column
3
- # Raised if length is less than 1
4
- class LengthError < StandardError; end
3
+ class LengthError < StandardError
4
+ end
5
5
 
6
- # Raised if name is empty
7
- class NameError < StandardError; end
6
+ class NameError < StandardError
7
+ end
8
8
 
9
9
  attr_reader :table, :name, :type, :length, :decimal
10
10
 
@@ -26,8 +26,8 @@ module DBF
26
26
  #
27
27
  # @param [String] name
28
28
  # @param [String] type
29
- # @param [Fixnum] length
30
- # @param [Fixnum] decimal
29
+ # @param [Integer] length
30
+ # @param [Integer] decimal
31
31
  def initialize(table, name, type, length, decimal)
32
32
  @table = table
33
33
  @name = clean(name)
@@ -41,14 +41,6 @@ module DBF
41
41
  validate_name
42
42
  end
43
43
 
44
- # Cast value to native type
45
- #
46
- # @param [String] value
47
- # @return [Fixnum, Float, Date, DateTime, Boolean, String]
48
- def type_cast(value)
49
- type_cast_class.type_cast(value)
50
- end
51
-
52
44
  # Returns true if the column is a memo
53
45
  #
54
46
  # @return [Boolean]
@@ -56,18 +48,19 @@ module DBF
56
48
  @memo ||= type == 'M'
57
49
  end
58
50
 
59
- # Schema definition
51
+ # Returns a Hash with :name, :type, :length, and :decimal keys
60
52
  #
61
- # @return [String]
62
- def schema_definition
63
- "\"#{underscored_name}\", #{schema_data_type}\n"
53
+ # @return [Hash]
54
+ def to_hash
55
+ {name: name, type: type, length: length, decimal: decimal}
64
56
  end
65
57
 
66
- # Sequel Schema definition
58
+ # Cast value to native type
67
59
  #
68
- # @return [String]
69
- def sequel_schema_definition
70
- ":#{underscored_name}, #{schema_data_type(:sequel)}\n"
60
+ # @param [String] value
61
+ # @return [Integer, Float, Date, DateTime, Boolean, String]
62
+ def type_cast(value)
63
+ type_cast_class.type_cast(value)
71
64
  end
72
65
 
73
66
  # Underscored name
@@ -78,27 +71,19 @@ module DBF
78
71
  # @return [String]
79
72
  def underscored_name
80
73
  @underscored_name ||= begin
81
- un = name.dup
82
- un.gsub!(/::/, '/')
83
- un.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
84
- un.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
85
- un.tr!('-', '_')
86
- un.downcase!
87
- un
74
+ name.gsub(/::/, '/')
75
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
76
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
77
+ .tr('-', '_')
78
+ .downcase
88
79
  end
89
80
  end
90
81
 
91
- def to_hash
92
- {name: name, type: type, length: length, decimal: decimal}
93
- end
94
-
95
82
  private
96
83
 
97
- def type_cast_class # nodoc
98
- @type_cast_class ||= begin
99
- klass = @length == 0 ? ColumnType::Nil : TYPE_CAST_CLASS[type.to_sym]
100
- klass.new(@decimal, @encoding)
101
- end
84
+ def clean(value) # nodoc
85
+ truncated_value = value.strip.partition("\x00").first
86
+ truncated_value.gsub(/[^\x20-\x7E]/, '')
102
87
  end
103
88
 
104
89
  def encode(value, strip_output = false) # nodoc
@@ -108,10 +93,6 @@ module DBF
108
93
  strip_output ? output.strip : output
109
94
  end
110
95
 
111
- def encode_string(string) # nodoc
112
- string.force_encoding(@encoding).encode(*encoding_args)
113
- end
114
-
115
96
  def encoding_args # nodoc
116
97
  @encoding_args ||= [
117
98
  Encoding.default_external,
@@ -119,6 +100,10 @@ module DBF
119
100
  ]
120
101
  end
121
102
 
103
+ def encode_string(string) # nodoc
104
+ string.force_encoding(@encoding).encode(*encoding_args)
105
+ end
106
+
122
107
  def schema_data_type(format = :activerecord) # nodoc
123
108
  case type
124
109
  when 'N', 'F'
@@ -146,9 +131,11 @@ module DBF
146
131
  end
147
132
  end
148
133
 
149
- def clean(value) # nodoc
150
- truncated_value = value.strip.partition("\x00").first
151
- truncated_value.gsub(/[^\x20-\x7E]/, '')
134
+ def type_cast_class # nodoc
135
+ @type_cast_class ||= begin
136
+ klass = @length == 0 ? ColumnType::Nil : TYPE_CAST_CLASS[type.to_sym]
137
+ klass.new(@decimal, @encoding)
138
+ end
152
139
  end
153
140
 
154
141
  def validate_length # nodoc
@@ -59,10 +59,7 @@ module DBF
59
59
 
60
60
  class Date < Base
61
61
  def type_cast(value)
62
- v = value.tr(' ', '0')
63
- v !~ /\S/ ? nil : ::Date.parse(v)
64
- rescue
65
- nil
62
+ value =~ /\d{8}/ && ::Date.strptime(value, '%Y%m%d')
66
63
  end
67
64
  end
68
65
 
@@ -43,9 +43,8 @@ module DBF
43
43
  # on any platform.
44
44
  # @return String
45
45
  def table_path(name)
46
- example = File.join(@dirname, "#{name}.dbf")
47
- glob = File.join(@dirname, '*')
48
- path = Dir.glob(glob).find { |match| match.downcase == example.downcase }
46
+ glob = File.join(@dirname, "#{name}.dbf")
47
+ path = Dir.glob(glob, File::FNM_CASEFOLD).first
49
48
 
50
49
  unless path && File.exist?(path)
51
50
  raise DBF::FileNotFoundError, "related table not found: #{name}"
@@ -22,21 +22,6 @@ module DBF
22
22
  other.respond_to?(:attributes) && other.attributes == attributes
23
23
  end
24
24
 
25
- # Maps a row to an array of values
26
- #
27
- # @return [Array]
28
- def to_a
29
- @columns.map { |column| attributes[column.name] }
30
- end
31
-
32
- # Do all search parameters match?
33
- #
34
- # @param [Hash] options
35
- # @return [Boolean]
36
- def match?(options)
37
- options.all? { |key, value| self[key] == value }
38
- end
39
-
40
25
  # Reads attributes by column name
41
26
  #
42
27
  # @param [String, Symbol] key
@@ -56,6 +41,14 @@ module DBF
56
41
  @attributes ||= Hash[attribute_map]
57
42
  end
58
43
 
44
+ # Do all search parameters match?
45
+ #
46
+ # @param [Hash] options
47
+ # @return [Boolean]
48
+ def match?(options)
49
+ options.all? { |key, value| self[key] == value }
50
+ end
51
+
59
52
  # Overrides standard Object.respond_to? to return true if a
60
53
  # matching column name is found.
61
54
  #
@@ -65,36 +58,24 @@ module DBF
65
58
  underscored_column_names.include?(method.to_s) || super
66
59
  end
67
60
 
61
+ # Maps a row to an array of values
62
+ #
63
+ # @return [Array]
64
+ def to_a
65
+ @columns.map { |column| attributes[column.name] }
66
+ end
67
+
68
68
  private
69
69
 
70
70
  def attribute_map # nodoc
71
71
  @columns.map { |column| [column.name, init_attribute(column)] }
72
72
  end
73
73
 
74
- def file_offset(attribute_name) # nodoc
75
- column = @columns.detect { |c| c.name == attribute_name.to_s }
76
- index = @columns.index(column)
77
- @columns[0, index + 1].compact.reduce(0) { |x, c| x += c.length }
78
- end
79
-
80
- def method_missing(method, *args) # nodoc
81
- if (index = underscored_column_names.index(method.to_s))
82
- attributes[@columns[index].name]
83
- else
84
- super
85
- end
86
- end
87
-
88
- def underscored_column_names # nodoc
89
- @underscored_column_names ||= @columns.map(&:underscored_name)
90
- end
91
-
92
- def init_attribute(column) # nodoc
93
- value = column.memo? ? memo(column) : get_data(column)
94
- column.type_cast(value)
74
+ def get_data(column) # nodoc
75
+ @data.read(column.length)
95
76
  end
96
77
 
97
- def memo(column) # nodoc
78
+ def get_memo(column) # nodoc
98
79
  if @memo
99
80
  @memo.get(memo_start_block(column))
100
81
  else
@@ -104,6 +85,11 @@ module DBF
104
85
  end
105
86
  end
106
87
 
88
+ def init_attribute(column) # nodoc
89
+ value = column.memo? ? get_memo(column) : get_data(column)
90
+ column.type_cast(value)
91
+ end
92
+
107
93
  def memo_start_block(column) # nodoc
108
94
  data = get_data(column)
109
95
  if %w(30 31).include?(@version)
@@ -112,8 +98,16 @@ module DBF
112
98
  data.to_i
113
99
  end
114
100
 
115
- def get_data(column) # nodoc
116
- @data.read(column.length)
101
+ def method_missing(method, *args) # nodoc
102
+ if (index = underscored_column_names.index(method.to_s))
103
+ attributes[@columns[index].name]
104
+ else
105
+ super
106
+ end
107
+ end
108
+
109
+ def underscored_column_names # nodoc
110
+ @underscored_column_names ||= @columns.map(&:underscored_name)
117
111
  end
118
112
  end
119
113
  end
@@ -32,23 +32,23 @@ module DBF
32
32
  end
33
33
  end
34
34
 
35
- def activerecord_schema(_table_only = false)
35
+ def activerecord_schema(_table_only = false) # nodoc
36
36
  s = "ActiveRecord::Schema.define do\n"
37
37
  s << " create_table \"#{name}\" do |t|\n"
38
38
  columns.each do |column|
39
- s << " t.column #{column.schema_definition}"
39
+ s << " t.column #{activerecord_schema_definition(column)}"
40
40
  end
41
41
  s << " end\nend"
42
42
  s
43
43
  end
44
44
 
45
- def sequel_schema(table_only = false)
45
+ def sequel_schema(table_only = false) # nodoc
46
46
  s = ''
47
47
  s << "Sequel.migration do\n" unless table_only
48
48
  s << " change do\n " unless table_only
49
49
  s << " create_table(:#{name}) do\n"
50
50
  columns.each do |column|
51
- s << " column #{column.sequel_schema_definition}"
51
+ s << " column #{sequel_schema_definition(column)}"
52
52
  end
53
53
  s << " end\n"
54
54
  s << " end\n" unless table_only
@@ -56,8 +56,51 @@ module DBF
56
56
  s
57
57
  end
58
58
 
59
- def json_schema(_table_only = false)
59
+ def json_schema(_table_only = false) # nodoc
60
60
  columns.map(&:to_hash).to_json
61
61
  end
62
+
63
+ # ActiveRecord schema definition
64
+ #
65
+ # @param [DBF::Column]
66
+ # @return [String]
67
+ def activerecord_schema_definition(column)
68
+ "\"#{column.underscored_name}\", #{schema_data_type(column, :activerecord)}\n"
69
+ end
70
+
71
+ # Sequel schema definition
72
+ #
73
+ # @params [DBF::Column]
74
+ # @return [String]
75
+ def sequel_schema_definition(column)
76
+ ":#{column.underscored_name}, #{schema_data_type(column, :sequel)}\n"
77
+ end
78
+
79
+ def schema_data_type(column, format = :activerecord) # nodoc
80
+ case column.type
81
+ when 'N', 'F'
82
+ column.decimal > 0 ? ':float' : ':integer'
83
+ when 'I'
84
+ ':integer'
85
+ when 'Y'
86
+ ':decimal, :precision => 15, :scale => 4'
87
+ when 'D'
88
+ ':date'
89
+ when 'T'
90
+ ':datetime'
91
+ when 'L'
92
+ ':boolean'
93
+ when 'M'
94
+ ':text'
95
+ when 'B'
96
+ ':binary'
97
+ else
98
+ if format == :sequel
99
+ ":varchar, :size => #{column.length}"
100
+ else
101
+ ":string, :limit => #{column.length}"
102
+ end
103
+ end
104
+ end
62
105
  end
63
106
  end
@@ -37,7 +37,6 @@ module DBF
37
37
  'fb' => 'FoxPro without memo file'
38
38
  }
39
39
 
40
- attr_reader :header
41
40
  attr_accessor :encoding
42
41
  attr_writer :name
43
42
 
@@ -64,18 +63,9 @@ module DBF
64
63
  # @param [optional String, Encoding] encoding Name of the encoding or an Encoding object
65
64
  def initialize(data, memo = nil, encoding = nil)
66
65
  @data = open_data(data)
67
- @data.rewind
68
- @header = Header.new(@data.read DBF_HEADER_SIZE)
69
66
  @encoding = encoding || header.encoding
70
67
  @memo = open_memo(data, memo)
71
68
  yield self if block_given?
72
- rescue Errno::ENOENT
73
- raise DBF::FileNotFoundError, "file not found: #{data}"
74
- end
75
-
76
- # @return [TrueClass, FalseClass]
77
- def has_memo_file?
78
- !!@memo
79
69
  end
80
70
 
81
71
  # Closes the table and memo file
@@ -95,14 +85,18 @@ module DBF
95
85
  end
96
86
  end
97
87
 
98
- # @return String
99
- def filename
100
- File.basename @data.path if @data.respond_to?(:path)
88
+ # Column names
89
+ #
90
+ # @return [String]
91
+ def column_names
92
+ columns.map(&:name)
101
93
  end
102
94
 
103
- # @return String
104
- def name
105
- @name ||= filename && File.basename(filename, ".*")
95
+ # All columns
96
+ #
97
+ # @return [Array]
98
+ def columns
99
+ @columns ||= build_columns
106
100
  end
107
101
 
108
102
  # Calls block once for each record in the table. The record may be nil
@@ -113,50 +107,9 @@ module DBF
113
107
  header.record_count.times { |i| yield record(i) }
114
108
  end
115
109
 
116
- # Retrieve a record by index number.
117
- # The record will be nil if it has been deleted, but not yet pruned from
118
- # the database.
119
- #
120
- # @param [Fixnum] index
121
- # @return [DBF::Record, NilClass]
122
- def record(index)
123
- seek_to_record(index)
124
- return nil if deleted_record?
125
- DBF::Record.new(@data.read(header.record_length), columns, version, @memo)
126
- end
127
-
128
- alias_method :row, :record
129
-
130
- # Internal dBase version number
131
- #
132
- # @return [String]
133
- def version
134
- @version ||= header.version
135
- end
136
-
137
- # Total number of records
138
- #
139
- # @return [Fixnum]
140
- def record_count
141
- @record_count ||= header.record_count
142
- end
143
-
144
- # Human readable version description
145
- #
146
110
  # @return [String]
147
- def version_description
148
- VERSIONS[version]
149
- end
150
-
151
- # Dumps all records to a CSV file. If no filename is given then CSV is
152
- # output to STDOUT.
153
- #
154
- # @param [optional String] path Defaults to STDOUT
155
- def to_csv(path = nil)
156
- out_io = path ? File.open(path, 'w') : $stdout
157
- csv = CSV.new(out_io, force_quotes: true)
158
- csv << column_names
159
- each { |record| csv << record.to_a }
111
+ def filename
112
+ File.basename(@data.path) if @data.respond_to?(:path)
160
113
  end
161
114
 
162
115
  # Find records using a simple ActiveRecord-like syntax.
@@ -181,12 +134,12 @@ module DBF
181
134
  # returned. The equivalent SQL would be "WHERE key1 = 'value1'
182
135
  # AND key2 = 'value2'".
183
136
  #
184
- # @param [Fixnum, Symbol] command
137
+ # @param [Integer, Symbol] command
185
138
  # @param [optional, Hash] options Hash of search parameters
186
139
  # @yield [optional, DBF::Record, NilClass]
187
140
  def find(command, options = {}, &block)
188
141
  case command
189
- when Fixnum
142
+ when Integer
190
143
  record(command)
191
144
  when Array
192
145
  command.map { |i| record(i) }
@@ -197,18 +150,60 @@ module DBF
197
150
  end
198
151
  end
199
152
 
200
- # All columns
153
+ # @return [TrueClass, FalseClass]
154
+ def has_memo_file?
155
+ !!@memo
156
+ end
157
+
158
+ # @return [String]
159
+ def name
160
+ @name ||= filename && File.basename(filename, ".*")
161
+ end
162
+
163
+ # Retrieve a record by index number.
164
+ # The record will be nil if it has been deleted, but not yet pruned from
165
+ # the database.
201
166
  #
202
- # @return [Array]
203
- def columns
204
- @columns ||= build_columns
167
+ # @param [Integer] index
168
+ # @return [DBF::Record, NilClass]
169
+ def record(index)
170
+ seek_to_record(index)
171
+ return nil if deleted_record?
172
+ DBF::Record.new(@data.read(header.record_length), columns, version, @memo)
205
173
  end
206
174
 
207
- # Column names
175
+ alias_method :row, :record
176
+
177
+ # Total number of records
178
+ #
179
+ # @return [Integer]
180
+ def record_count
181
+ @record_count ||= header.record_count
182
+ end
183
+
184
+ # Dumps all records to a CSV file. If no filename is given then CSV is
185
+ # output to STDOUT.
186
+ #
187
+ # @param [optional String] path Defaults to STDOUT
188
+ def to_csv(path = nil)
189
+ out_io = path ? File.open(path, 'w') : $stdout
190
+ csv = CSV.new(out_io, force_quotes: true)
191
+ csv << column_names
192
+ each { |record| csv << record.to_a }
193
+ end
194
+
195
+ # Internal dBase version number
208
196
  #
209
197
  # @return [String]
210
- def column_names
211
- columns.map(&:name)
198
+ def version
199
+ @version ||= header.version
200
+ end
201
+
202
+ # Human readable version description
203
+ #
204
+ # @return [String]
205
+ def version_description
206
+ VERSIONS[version]
212
207
  end
213
208
 
214
209
  private
@@ -224,6 +219,11 @@ module DBF
224
219
  columns
225
220
  end
226
221
 
222
+ def deleted_record? # nodoc
223
+ flag = @data.read(1)
224
+ flag ? flag.unpack('a') == ['*'] : true
225
+ end
226
+
227
227
  def end_of_record? # nodoc
228
228
  original_pos = @data.pos
229
229
  byte = @data.read(1)
@@ -231,10 +231,27 @@ module DBF
231
231
  byte.ord == 13
232
232
  end
233
233
 
234
+ def find_all(options) # nodoc
235
+ map do |record|
236
+ if record && record.match?(options)
237
+ yield record if block_given?
238
+ record
239
+ end
240
+ end.compact
241
+ end
242
+
243
+ def find_first(options) # nodoc
244
+ detect { |record| record && record.match?(options) }
245
+ end
246
+
234
247
  def foxpro? # nodoc
235
248
  FOXPRO_VERSIONS.keys.include? version
236
249
  end
237
250
 
251
+ def header
252
+ @header ||= Header.new(@data.read DBF_HEADER_SIZE)
253
+ end
254
+
238
255
  def memo_class # nodoc
239
256
  @memo_class ||= begin
240
257
  if foxpro?
@@ -245,8 +262,16 @@ module DBF
245
262
  end
246
263
  end
247
264
 
265
+ def memo_search_path(io) # nodoc
266
+ dirname = File.dirname(io)
267
+ basename = File.basename(io, '.*')
268
+ "#{dirname}/#{basename}*.{fpt,FPT,dbt,DBT}"
269
+ end
270
+
248
271
  def open_data(data) # nodoc
249
272
  data.is_a?(StringIO) ? data : File.open(data, 'rb')
273
+ rescue Errno::ENOENT
274
+ raise DBF::FileNotFoundError, "file not found: #{data}"
250
275
  end
251
276
 
252
277
  def open_memo(data, memo = nil) # nodoc
@@ -259,32 +284,8 @@ module DBF
259
284
  end
260
285
  end
261
286
 
262
- def memo_search_path(io) # nodoc
263
- dirname = File.dirname(io)
264
- basename = File.basename(io, '.*')
265
- "#{dirname}/#{basename}*.{fpt,FPT,dbt,DBT}"
266
- end
267
-
268
- def find_all(options) # nodoc
269
- map do |record|
270
- if record && record.match?(options)
271
- yield record if block_given?
272
- record
273
- end
274
- end.compact
275
- end
276
-
277
- def find_first(options) # nodoc
278
- detect { |record| record && record.match?(options) }
279
- end
280
-
281
- def deleted_record? # nodoc
282
- flag = @data.read(1)
283
- flag ? flag.unpack('a') == ['*'] : true
284
- end
285
-
286
287
  def seek(offset) # nodoc
287
- @data.seek header.header_length + offset
288
+ @data.seek(header.header_length + offset)
288
289
  end
289
290
 
290
291
  def seek_to_record(index) # nodoc
@@ -1,3 +1,3 @@
1
1
  module DBF
2
- VERSION = '3.1.0'
2
+ VERSION = '3.1.1'
3
3
  end
@@ -60,13 +60,13 @@ RSpec.describe DBF::Column do
60
60
  end
61
61
 
62
62
  context 'and 0 decimals' do
63
- it 'casts value to Fixnum' do
63
+ it 'casts value to Integer' do
64
64
  value = '135'
65
65
  column = DBF::Column.new table, 'ColumnName', 'N', 3, 0
66
66
  expect(column.type_cast(value)).to eq 135
67
67
  end
68
68
 
69
- it 'supports negative Fixnum' do
69
+ it 'supports negative Integer' do
70
70
  value = '-135'
71
71
  column = DBF::Column.new table, 'ColumnName', 'N', 3, 0
72
72
  expect(column.type_cast(value)).to eq (-135)
@@ -139,13 +139,13 @@ RSpec.describe DBF::Column do
139
139
  end
140
140
  end
141
141
 
142
- it 'casts value to Fixnum' do
142
+ it 'casts value to Integer' do
143
143
  value = "\203\171\001\000"
144
144
  column = DBF::Column.new table, 'ColumnName', 'I', 3, 0
145
145
  expect(column.type_cast(value)).to eq 96643
146
146
  end
147
147
 
148
- it 'supports negative Fixnum' do
148
+ it 'supports negative Integer' do
149
149
  value = "\x24\xE1\xFF\xFF"
150
150
  column = DBF::Column.new table, 'ColumnName', 'I', 3, 0
151
151
  expect(column.type_cast(value)).to eq (-7900)
@@ -284,54 +284,6 @@ RSpec.describe DBF::Column do
284
284
  end
285
285
  end
286
286
 
287
- context '#schema_definition' do
288
- context 'with type N (number)' do
289
- it 'outputs an integer column' do
290
- column = DBF::Column.new table, 'ColumnName', 'N', 1, 0
291
- expect(column.schema_definition).to eq "\"column_name\", :integer\n"
292
- end
293
- end
294
-
295
- context 'with type B (binary)' do
296
- context 'with Foxpro dbf' do
297
- it 'outputs a float column' do
298
- column = DBF::Column.new table, 'ColumnName', 'B', 1, 2
299
- expect(column.schema_definition).to eq "\"column_name\", :binary\n"
300
- end
301
- end
302
- end
303
-
304
- it 'defines a float colmn if type is (N)umber with more than 0 decimals' do
305
- column = DBF::Column.new table, 'ColumnName', 'N', 1, 2
306
- expect(column.schema_definition).to eq "\"column_name\", :float\n"
307
- end
308
-
309
- it 'defines a date column if type is (D)ate' do
310
- column = DBF::Column.new table, 'ColumnName', 'D', 8, 0
311
- expect(column.schema_definition).to eq "\"column_name\", :date\n"
312
- end
313
-
314
- it 'defines a datetime column if type is (D)ate' do
315
- column = DBF::Column.new table, 'ColumnName', 'T', 16, 0
316
- expect(column.schema_definition).to eq "\"column_name\", :datetime\n"
317
- end
318
-
319
- it 'defines a boolean column if type is (L)ogical' do
320
- column = DBF::Column.new table, 'ColumnName', 'L', 1, 0
321
- expect(column.schema_definition).to eq "\"column_name\", :boolean\n"
322
- end
323
-
324
- it 'defines a text column if type is (M)emo' do
325
- column = DBF::Column.new table, 'ColumnName', 'M', 1, 0
326
- expect(column.schema_definition).to eq "\"column_name\", :text\n"
327
- end
328
-
329
- it 'defines a string column with length for any other data types' do
330
- column = DBF::Column.new table, 'ColumnName', 'X', 20, 0
331
- expect(column.schema_definition).to eq "\"column_name\", :string, :limit => 20\n"
332
- end
333
- end
334
-
335
287
  context '#name' do
336
288
  it 'contains only ASCII characters' do
337
289
  column = DBF::Column.new table, "--\x1F-\x68\x65\x6C\x6C\x6F world-\x80--", 'N', 1, 0
@@ -31,9 +31,9 @@ RSpec.shared_examples_for 'DBF' do
31
31
  end
32
32
  end
33
33
 
34
- specify "column lengths should be instances of Fixnum" do
34
+ specify "column lengths should be instances of Integer" do
35
35
  table.columns.each do |column|
36
- expect(column.length).to be_kind_of(Fixnum)
36
+ expect(column.length).to be_kind_of(Integer)
37
37
  end
38
38
  end
39
39
 
@@ -43,9 +43,9 @@ RSpec.shared_examples_for 'DBF' do
43
43
  end
44
44
  end
45
45
 
46
- specify "column decimals should be instances of Fixnum" do
46
+ specify "column decimals should be instances of Integer" do
47
47
  table.columns.each do |column|
48
- expect(column.decimal).to be_kind_of(Fixnum)
48
+ expect(column.decimal).to be_kind_of(Integer)
49
49
  end
50
50
  end
51
51
  end
@@ -326,4 +326,52 @@ SCHEMA
326
326
  expect(table.column_names).to eq column_names
327
327
  end
328
328
  end
329
+
330
+ context '#activerecord_schema_definition' do
331
+ context 'with type N (number)' do
332
+ it 'outputs an integer column' do
333
+ column = DBF::Column.new table, 'ColumnName', 'N', 1, 0
334
+ expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :integer\n"
335
+ end
336
+ end
337
+
338
+ context 'with type B (binary)' do
339
+ context 'with Foxpro dbf' do
340
+ it 'outputs a float column' do
341
+ column = DBF::Column.new table, 'ColumnName', 'B', 1, 2
342
+ expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :binary\n"
343
+ end
344
+ end
345
+ end
346
+
347
+ it 'defines a float colmn if type is (N)umber with more than 0 decimals' do
348
+ column = DBF::Column.new table, 'ColumnName', 'N', 1, 2
349
+ expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :float\n"
350
+ end
351
+
352
+ it 'defines a date column if type is (D)ate' do
353
+ column = DBF::Column.new table, 'ColumnName', 'D', 8, 0
354
+ expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :date\n"
355
+ end
356
+
357
+ it 'defines a datetime column if type is (D)ate' do
358
+ column = DBF::Column.new table, 'ColumnName', 'T', 16, 0
359
+ expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :datetime\n"
360
+ end
361
+
362
+ it 'defines a boolean column if type is (L)ogical' do
363
+ column = DBF::Column.new table, 'ColumnName', 'L', 1, 0
364
+ expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :boolean\n"
365
+ end
366
+
367
+ it 'defines a text column if type is (M)emo' do
368
+ column = DBF::Column.new table, 'ColumnName', 'M', 1, 0
369
+ expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :text\n"
370
+ end
371
+
372
+ it 'defines a string column with length for any other data types' do
373
+ column = DBF::Column.new table, 'ColumnName', 'X', 20, 0
374
+ expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :string, :limit => 20\n"
375
+ end
376
+ end
329
377
  end
@@ -4,7 +4,7 @@ begin
4
4
  rescue LoadError
5
5
  end
6
6
 
7
- Encoding.default_external = "UTF-8"
7
+ Encoding.default_external = 'UTF-8'
8
8
 
9
9
  require 'dbf'
10
10
  require 'yaml'
@@ -26,7 +26,7 @@ RSpec.configure do |config|
26
26
  end
27
27
 
28
28
  def fixture_path
29
- @fixture_path ||= File.join(File.dirname(__FILE__), '/fixtures')
29
+ @fixture_path ||= File.join(File.dirname(__FILE__), 'fixtures')
30
30
  end
31
31
 
32
32
  def fixture(filename)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbf
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-30 00:00:00.000000000 Z
11
+ date: 2017-10-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A small fast library for reading dBase, xBase, Clipper and FoxPro database
14
14
  files.
@@ -110,13 +110,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
110
  version: 1.3.0
111
111
  requirements: []
112
112
  rubyforge_project:
113
- rubygems_version: 2.5.1
113
+ rubygems_version: 2.6.11
114
114
  signing_key:
115
115
  specification_version: 4
116
116
  summary: Read xBase files
117
117
  test_files:
118
- - spec/dbf/column_spec.rb
119
- - spec/dbf/database_spec.rb
120
118
  - spec/dbf/file_formats_spec.rb
119
+ - spec/dbf/database_spec.rb
120
+ - spec/dbf/column_spec.rb
121
121
  - spec/dbf/record_spec.rb
122
122
  - spec/dbf/table_spec.rb