dbf 4.1.3 → 4.1.4

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +63 -52
  5. data/LICENSE +1 -1
  6. data/README.md +109 -16
  7. data/docs/CNAME +1 -0
  8. data/docs/DBF.html +200 -0
  9. data/docs/DBF/Column.html +947 -0
  10. data/docs/DBF/Column/LengthError.html +124 -0
  11. data/docs/DBF/Column/NameError.html +124 -0
  12. data/docs/DBF/ColumnType.html +115 -0
  13. data/docs/DBF/ColumnType/Base.html +389 -0
  14. data/docs/DBF/ColumnType/Boolean.html +238 -0
  15. data/docs/DBF/ColumnType/Currency.html +238 -0
  16. data/docs/DBF/ColumnType/Date.html +242 -0
  17. data/docs/DBF/ColumnType/DateTime.html +246 -0
  18. data/docs/DBF/ColumnType/Double.html +238 -0
  19. data/docs/DBF/ColumnType/Float.html +238 -0
  20. data/docs/DBF/ColumnType/General.html +238 -0
  21. data/docs/DBF/ColumnType/Memo.html +246 -0
  22. data/docs/DBF/ColumnType/Nil.html +238 -0
  23. data/docs/DBF/ColumnType/Number.html +242 -0
  24. data/docs/DBF/ColumnType/SignedLong.html +238 -0
  25. data/docs/DBF/ColumnType/String.html +240 -0
  26. data/docs/DBF/Database.html +126 -0
  27. data/docs/DBF/Database/Foxpro.html +653 -0
  28. data/docs/DBF/Database/Table.html +346 -0
  29. data/docs/DBF/FileNotFoundError.html +124 -0
  30. data/docs/DBF/Header.html +723 -0
  31. data/docs/DBF/Memo.html +117 -0
  32. data/docs/DBF/Memo/Base.html +485 -0
  33. data/docs/DBF/Memo/Dbase3.html +242 -0
  34. data/docs/DBF/Memo/Dbase4.html +230 -0
  35. data/docs/DBF/Memo/Foxpro.html +268 -0
  36. data/docs/DBF/NoColumnsDefined.html +124 -0
  37. data/docs/DBF/Record.html +773 -0
  38. data/docs/DBF/Schema.html +980 -0
  39. data/docs/DBF/Table.html +1571 -0
  40. data/docs/_index.html +415 -0
  41. data/docs/class_list.html +51 -0
  42. data/docs/css/common.css +1 -0
  43. data/docs/css/full_list.css +58 -0
  44. data/docs/css/style.css +497 -0
  45. data/docs/file.README.html +359 -0
  46. data/docs/file_list.html +56 -0
  47. data/docs/frames.html +17 -0
  48. data/docs/index.html +359 -0
  49. data/docs/js/app.js +314 -0
  50. data/docs/js/full_list.js +216 -0
  51. data/docs/js/jquery.js +4 -0
  52. data/docs/method_list.html +675 -0
  53. data/docs/top-level-namespace.html +110 -0
  54. data/lib/dbf/column.rb +5 -4
  55. data/lib/dbf/column_type.rb +26 -0
  56. data/lib/dbf/database/foxpro.rb +7 -2
  57. data/lib/dbf/header.rb +11 -3
  58. data/lib/dbf/record.rb +5 -5
  59. data/lib/dbf/schema.rb +4 -3
  60. data/lib/dbf/table.rb +25 -11
  61. data/lib/dbf/version.rb +1 -1
  62. data/spec/dbf/file_formats_spec.rb +18 -0
  63. data/spec/fixtures/dbase_02.dbf +0 -0
  64. metadata +52 -6
  65. data/docs/supported_encodings.csv +0 -60
  66. data/docs/supported_types.markdown +0 -38
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.9.26
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" />
16
+
17
+ <script type="text/javascript">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="DBF.html" title="DBF (module)">DBF</a></span>
86
+
87
+
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Sun Aug 8 16:19:07 2021 by
104
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.26 (ruby-3.0.1).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
data/lib/dbf/column.rb CHANGED
@@ -28,10 +28,11 @@ module DBF
28
28
 
29
29
  # Initialize a new DBF::Column
30
30
  #
31
- # @param [String] name
32
- # @param [String] type
33
- # @param [Integer] length
34
- # @param [Integer] decimal
31
+ # @param table [String]
32
+ # @param name [String]
33
+ # @param type [String]
34
+ # @param length [Integer]
35
+ # @param decimal [Integer]
35
36
  def initialize(table, name, type, length, decimal)
36
37
  @table = table
37
38
  @name = clean(name)
@@ -3,6 +3,8 @@ module DBF
3
3
  class Base
4
4
  attr_reader :decimal, :encoding
5
5
 
6
+ # @param decimal [Integer]
7
+ # @param encoding [String, Encoding]
6
8
  def initialize(decimal, encoding)
7
9
  @decimal = decimal
8
10
  @encoding = encoding
@@ -10,12 +12,16 @@ module DBF
10
12
  end
11
13
 
12
14
  class Nil < Base
15
+
16
+ # @param _value [String]
13
17
  def type_cast(_value)
14
18
  nil
15
19
  end
16
20
  end
17
21
 
18
22
  class Number < Base
23
+
24
+ # @param value [String]
19
25
  def type_cast(value)
20
26
  return nil if value.strip.empty?
21
27
 
@@ -24,36 +30,48 @@ module DBF
24
30
  end
25
31
 
26
32
  class Currency < Base
33
+
34
+ # @param value [String]
27
35
  def type_cast(value)
28
36
  (value.unpack1('q<') / 10_000.0).to_f
29
37
  end
30
38
  end
31
39
 
32
40
  class SignedLong < Base
41
+
42
+ # @param value [String]
33
43
  def type_cast(value)
34
44
  value.unpack1('l<')
35
45
  end
36
46
  end
37
47
 
38
48
  class Float < Base
49
+
50
+ # @param value [String]
39
51
  def type_cast(value)
40
52
  value.to_f
41
53
  end
42
54
  end
43
55
 
44
56
  class Double < Base
57
+
58
+ # @param value [String]
45
59
  def type_cast(value)
46
60
  value.unpack1('E')
47
61
  end
48
62
  end
49
63
 
50
64
  class Boolean < Base
65
+
66
+ # @param value [String]
51
67
  def type_cast(value)
52
68
  value.strip.match?(/^(y|t)$/i)
53
69
  end
54
70
  end
55
71
 
56
72
  class Date < Base
73
+
74
+ # @param value [String]
57
75
  def type_cast(value)
58
76
  value.match?(/\d{8}/) && ::Date.strptime(value, '%Y%m%d')
59
77
  rescue StandardError
@@ -62,6 +80,8 @@ module DBF
62
80
  end
63
81
 
64
82
  class DateTime < Base
83
+
84
+ # @param value [String]
65
85
  def type_cast(value)
66
86
  days, msecs = value.unpack('l2')
67
87
  secs = (msecs / 1000).to_i
@@ -72,6 +92,8 @@ module DBF
72
92
  end
73
93
 
74
94
  class Memo < Base
95
+
96
+ # @param value [String]
75
97
  def type_cast(value)
76
98
  if encoding && !value.nil?
77
99
  value.force_encoding(@encoding).encode(Encoding.default_external, undef: :replace, invalid: :replace)
@@ -82,12 +104,16 @@ module DBF
82
104
  end
83
105
 
84
106
  class General < Base
107
+
108
+ # @param value [String]
85
109
  def type_cast(value)
86
110
  value
87
111
  end
88
112
  end
89
113
 
90
114
  class String < Base
115
+
116
+ # @param value [String]
91
117
  def type_cast(value)
92
118
  value = value.strip
93
119
  @encoding ? value.force_encoding(@encoding).encode(Encoding.default_external, undef: :replace, invalid: :replace) : value
@@ -16,6 +16,8 @@ module DBF
16
16
  #
17
17
  # # Calling a table
18
18
  # contacts = db.contacts.record(0)
19
+ #
20
+ # @param path [String]
19
21
  def initialize(path)
20
22
  @path = path
21
23
  @dirname = File.dirname(@path)
@@ -30,7 +32,9 @@ module DBF
30
32
  end
31
33
 
32
34
  # Returns table with given name
33
- # @return Table
35
+ #
36
+ # @param name [String]
37
+ # @return [DBF::Table]
34
38
  def table(name)
35
39
  Table.new table_path(name) do |table|
36
40
  table.long_names = @tables[name]
@@ -40,7 +44,8 @@ module DBF
40
44
  # Searches the database directory for the table's dbf file
41
45
  # and returns the absolute path. Ensures case-insensitivity
42
46
  # on any platform.
43
- # @return String
47
+ # @param name [String]
48
+ # @return [String]
44
49
  def table_path(name)
45
50
  glob = File.join(@dirname, "#{name}.dbf")
46
51
  path = Dir.glob(glob, File::FNM_CASEFOLD).first
data/lib/dbf/header.rb CHANGED
@@ -9,12 +9,20 @@ module DBF
9
9
 
10
10
  def initialize(data)
11
11
  @data = data
12
- @version, @record_count, @header_length, @record_length, @encoding_key = unpack_header
13
- @encoding = DBF::ENCODINGS[@encoding_key]
12
+ unpack_header
14
13
  end
15
14
 
16
15
  def unpack_header
17
- @data.unpack('H2 x3 V v2 x17H2')
16
+ @version = @data.unpack('H2').first
17
+
18
+ case @version
19
+ when '02'
20
+ @record_count, @record_length = @data.unpack('x v x3 v')
21
+ @header_length = 521
22
+ else
23
+ @record_count, @header_length, @record_length, @encoding_key = @data.unpack('x x3 V v2 x17 H2')
24
+ @encoding = DBF::ENCODINGS[@encoding_key]
25
+ end
18
26
  end
19
27
  end
20
28
  end
data/lib/dbf/record.rb CHANGED
@@ -3,10 +3,10 @@ module DBF
3
3
  class Record
4
4
  # Initialize a new DBF::Record
5
5
  #
6
- # @data [String, StringIO] data
7
- # @columns [Column]
8
- # @version [String]
9
- # @memo [DBF::Memo]
6
+ # @param data [String, StringIO] data
7
+ # @param columns [Column]
8
+ # @param version [String]
9
+ # @param memo [DBF::Memo]
10
10
  def initialize(data, columns, version, memo)
11
11
  @data = StringIO.new(data)
12
12
  @columns = columns
@@ -24,7 +24,7 @@ module DBF
24
24
 
25
25
  # Reads attributes by column name
26
26
  #
27
- # @param [String, Symbol] key
27
+ # @param name [String, Symbol] key
28
28
  def [](name)
29
29
  key = name.to_s
30
30
  if attributes.key?(key)
data/lib/dbf/schema.rb CHANGED
@@ -32,7 +32,8 @@ module DBF
32
32
  # t.column :notes, :text
33
33
  # end
34
34
  #
35
- # @param [Symbol] format Valid options are :activerecord and :json
35
+ # @param format [Symbol] format Valid options are :activerecord and :json
36
+ # @param table_only [Boolean]
36
37
  # @return [String]
37
38
  def schema(format = :activerecord, table_only = false)
38
39
  schema_method_name = schema_name(format)
@@ -75,7 +76,7 @@ module DBF
75
76
 
76
77
  # ActiveRecord schema definition
77
78
  #
78
- # @param [DBF::Column]
79
+ # @param column [DBF::Column]
79
80
  # @return [String]
80
81
  def activerecord_schema_definition(column)
81
82
  "\"#{column.underscored_name}\", #{schema_data_type(column, :activerecord)}\n"
@@ -83,7 +84,7 @@ module DBF
83
84
 
84
85
  # Sequel schema definition
85
86
  #
86
- # @params [DBF::Column]
87
+ # @param column [DBF::Column]
87
88
  # @return [String]
88
89
  def sequel_schema_definition(column)
89
90
  ":#{column.underscored_name}, #{schema_data_type(column, :sequel)}\n"
data/lib/dbf/table.rb CHANGED
@@ -12,7 +12,8 @@ module DBF
12
12
  include Enumerable
13
13
  include ::DBF::Schema
14
14
 
15
- DBF_HEADER_SIZE = 32
15
+ DBASE2_HEADER_SIZE = 8
16
+ DBASE3_HEADER_SIZE = 32
16
17
 
17
18
  VERSIONS = {
18
19
  '02' => 'FoxBase',
@@ -67,9 +68,9 @@ module DBF
67
68
  # table = DBF::Table.new 'data.dbf', nil, 'cp437'
68
69
  # table = DBF::Table.new 'data.dbf', 'memo.dbt', Encoding::US_ASCII
69
70
  #
70
- # @param [String, StringIO] data Path to the dbf file or a StringIO object
71
- # @param [optional String, StringIO] memo Path to the memo file or a StringIO object
72
- # @param [optional String, Encoding] encoding Name of the encoding or an Encoding object
71
+ # @param data [String, StringIO] data Path to the dbf file or a StringIO object
72
+ # @param memo [optional String, StringIO] memo Path to the memo file or a StringIO object
73
+ # @param encoding [optional String, Encoding] encoding Name of the encoding or an Encoding object
73
74
  def initialize(data, memo = nil, encoding = nil)
74
75
  @data = open_data(data)
75
76
  @encoding = encoding || header.encoding
@@ -98,7 +99,7 @@ module DBF
98
99
  #
99
100
  # @return [String]
100
101
  def column_names
101
- columns.map(&:name)
102
+ @column_names ||= columns.map(&:name)
102
103
  end
103
104
 
104
105
  # All columns
@@ -145,8 +146,8 @@ module DBF
145
146
  # returned. The equivalent SQL would be "WHERE key1 = 'value1'
146
147
  # AND key2 = 'value2'".
147
148
  #
148
- # @param [Integer, Symbol] command
149
- # @param [optional, Hash] options Hash of search parameters
149
+ # @param command [Integer, Symbol] command
150
+ # @param options [optional, Hash] options Hash of search parameters
150
151
  # @yield [optional, DBF::Record, NilClass]
151
152
  def find(command, options = {}, &block)
152
153
  case command
@@ -211,16 +212,29 @@ module DBF
211
212
 
212
213
  def build_columns # :nodoc:
213
214
  safe_seek do
214
- @data.seek(DBF_HEADER_SIZE)
215
+ @data.seek(header_size)
215
216
  [].tap do |columns|
216
217
  until end_of_record?
217
- column_data = @data.read(DBF_HEADER_SIZE)
218
- columns << Column.new(self, *column_data.unpack('a10 x a x4 C2'))
218
+ case version
219
+ when '02'
220
+ column_data = @data.read(header_size * 2)
221
+ columns << Column.new(self, *column_data.unpack('A11 a C'), 0)
222
+ else
223
+ column_data = @data.read(header_size)
224
+ columns << Column.new(self, *column_data.unpack('A11 a x4 C2'))
225
+ end
219
226
  end
220
227
  end
221
228
  end
222
229
  end
223
230
 
231
+ def header_size
232
+ header_size = case version
233
+ when '02' then DBASE2_HEADER_SIZE
234
+ else DBASE3_HEADER_SIZE
235
+ end
236
+ end
237
+
224
238
  def deleted_record? # :nodoc:
225
239
  flag = @data.read(1)
226
240
  flag ? flag.unpack1('a') == '*' : true
@@ -250,7 +264,7 @@ module DBF
250
264
  def header # :nodoc:
251
265
  @header ||= safe_seek do
252
266
  @data.seek(0)
253
- Header.new(@data.read(DBF_HEADER_SIZE))
267
+ Header.new(@data.read(DBASE3_HEADER_SIZE))
254
268
  end
255
269
  end
256
270
 
data/lib/dbf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module DBF
2
- VERSION = '4.1.3'.freeze
2
+ VERSION = '4.1.4'.freeze
3
3
  end
@@ -48,6 +48,24 @@ RSpec.shared_examples_for 'DBF' do
48
48
  end
49
49
  end
50
50
 
51
+ RSpec.describe DBF, 'of type 02 (FoxBase)' do
52
+ let(:table) { DBF::Table.new fixture('dbase_02.dbf') }
53
+
54
+ it_behaves_like 'DBF'
55
+
56
+ it 'reports the correct version number' do
57
+ expect(table.version).to eq '02'
58
+ end
59
+
60
+ it 'reports the correct version description' do
61
+ expect(table.version_description).to eq 'FoxBase'
62
+ end
63
+
64
+ it 'determines the number of records' do
65
+ expect(table.record_count).to eq 9
66
+ end
67
+ end
68
+
51
69
  RSpec.describe DBF, 'of type 03 (dBase III without memo file)' do
52
70
  let(:table) { DBF::Table.new fixture('dbase_03.dbf') }
53
71