dbf 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
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