dbf 4.1.3 → 4.2.0

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +166 -104
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +63 -52
  5. data/LICENSE +1 -1
  6. data/README.md +110 -16
  7. data/bin/dbf +2 -0
  8. data/docs/CNAME +1 -0
  9. data/docs/DBF.html +200 -0
  10. data/docs/DBF/Column.html +947 -0
  11. data/docs/DBF/Column/LengthError.html +124 -0
  12. data/docs/DBF/Column/NameError.html +124 -0
  13. data/docs/DBF/ColumnType.html +115 -0
  14. data/docs/DBF/ColumnType/Base.html +389 -0
  15. data/docs/DBF/ColumnType/Boolean.html +238 -0
  16. data/docs/DBF/ColumnType/Currency.html +238 -0
  17. data/docs/DBF/ColumnType/Date.html +242 -0
  18. data/docs/DBF/ColumnType/DateTime.html +246 -0
  19. data/docs/DBF/ColumnType/Double.html +238 -0
  20. data/docs/DBF/ColumnType/Float.html +238 -0
  21. data/docs/DBF/ColumnType/General.html +238 -0
  22. data/docs/DBF/ColumnType/Memo.html +246 -0
  23. data/docs/DBF/ColumnType/Nil.html +238 -0
  24. data/docs/DBF/ColumnType/Number.html +242 -0
  25. data/docs/DBF/ColumnType/SignedLong.html +238 -0
  26. data/docs/DBF/ColumnType/String.html +240 -0
  27. data/docs/DBF/Database.html +126 -0
  28. data/docs/DBF/Database/Foxpro.html +653 -0
  29. data/docs/DBF/Database/Table.html +346 -0
  30. data/docs/DBF/FileNotFoundError.html +124 -0
  31. data/docs/DBF/Header.html +723 -0
  32. data/docs/DBF/Memo.html +117 -0
  33. data/docs/DBF/Memo/Base.html +485 -0
  34. data/docs/DBF/Memo/Dbase3.html +242 -0
  35. data/docs/DBF/Memo/Dbase4.html +230 -0
  36. data/docs/DBF/Memo/Foxpro.html +268 -0
  37. data/docs/DBF/NoColumnsDefined.html +124 -0
  38. data/docs/DBF/Record.html +773 -0
  39. data/docs/DBF/Schema.html +980 -0
  40. data/docs/DBF/Table.html +1571 -0
  41. data/docs/_index.html +415 -0
  42. data/docs/class_list.html +51 -0
  43. data/docs/css/common.css +1 -0
  44. data/docs/css/full_list.css +58 -0
  45. data/docs/css/style.css +497 -0
  46. data/docs/file.README.html +359 -0
  47. data/docs/file_list.html +56 -0
  48. data/docs/frames.html +17 -0
  49. data/docs/index.html +359 -0
  50. data/docs/js/app.js +314 -0
  51. data/docs/js/full_list.js +216 -0
  52. data/docs/js/jquery.js +4 -0
  53. data/docs/method_list.html +675 -0
  54. data/docs/top-level-namespace.html +110 -0
  55. data/lib/dbf/column.rb +8 -7
  56. data/lib/dbf/column_type.rb +23 -0
  57. data/lib/dbf/database/foxpro.rb +7 -2
  58. data/lib/dbf/header.rb +11 -3
  59. data/lib/dbf/record.rb +5 -5
  60. data/lib/dbf/schema.rb +4 -3
  61. data/lib/dbf/table.rb +34 -11
  62. data/lib/dbf/version.rb +1 -1
  63. data/spec/dbf/column_spec.rb +3 -3
  64. data/spec/dbf/file_formats_spec.rb +54 -0
  65. data/spec/fixtures/dbase_02.dbf +0 -0
  66. data/spec/fixtures/dbase_02_summary.txt +23 -0
  67. data/spec/fixtures/dbase_32.dbf +0 -0
  68. data/spec/fixtures/dbase_32_summary.txt +11 -0
  69. data/spec/fixtures/dbase_8c.dbf +0 -0
  70. metadata +56 -6
  71. data/docs/supported_encodings.csv +0 -60
  72. 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
@@ -21,17 +21,19 @@ module DBF
21
21
  L: ColumnType::Boolean,
22
22
  M: ColumnType::Memo,
23
23
  B: ColumnType::Double,
24
- G: ColumnType::General
24
+ G: ColumnType::General,
25
+ '+'.to_sym => ColumnType::SignedLong2
25
26
  }
26
27
  TYPE_CAST_CLASS.default = ColumnType::String
27
28
  TYPE_CAST_CLASS.freeze
28
29
 
29
30
  # Initialize a new DBF::Column
30
31
  #
31
- # @param [String] name
32
- # @param [String] type
33
- # @param [Integer] length
34
- # @param [Integer] decimal
32
+ # @param table [String]
33
+ # @param name [String]
34
+ # @param type [String]
35
+ # @param length [Integer]
36
+ # @param decimal [Integer]
35
37
  def initialize(table, name, type, length, decimal)
36
38
  @table = table
37
39
  @name = clean(name)
@@ -78,8 +80,7 @@ module DBF
78
80
  private
79
81
 
80
82
  def clean(value) # :nodoc:
81
- truncated_value = value.strip.partition("\x00").first
82
- truncated_value.gsub(/[^\x20-\x7E]/, '')
83
+ value.strip.gsub("\x00", '').gsub(/[^\x20-\x7E]/, '')
83
84
  end
84
85
 
85
86
  def encode(value, strip_output = false) # :nodoc:
@@ -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,14 @@ module DBF
10
12
  end
11
13
 
12
14
  class Nil < Base
15
+ # @param _value [String]
13
16
  def type_cast(_value)
14
17
  nil
15
18
  end
16
19
  end
17
20
 
18
21
  class Number < Base
22
+ # @param value [String]
19
23
  def type_cast(value)
20
24
  return nil if value.strip.empty?
21
25
 
@@ -24,36 +28,51 @@ module DBF
24
28
  end
25
29
 
26
30
  class Currency < Base
31
+ # @param value [String]
27
32
  def type_cast(value)
28
33
  (value.unpack1('q<') / 10_000.0).to_f
29
34
  end
30
35
  end
31
36
 
32
37
  class SignedLong < Base
38
+ # @param value [String]
33
39
  def type_cast(value)
34
40
  value.unpack1('l<')
35
41
  end
36
42
  end
37
43
 
44
+ class SignedLong2 < Base
45
+ # @param value [String]
46
+ def type_cast(value)
47
+ s = value.unpack1('B*')
48
+ sign_multiplier = s[0] == '0' ? -1 : 1
49
+ s[1, 31].to_i(2) * sign_multiplier
50
+ end
51
+ end
52
+
38
53
  class Float < Base
54
+ # @param value [String]
39
55
  def type_cast(value)
40
56
  value.to_f
41
57
  end
42
58
  end
43
59
 
44
60
  class Double < Base
61
+ # @param value [String]
45
62
  def type_cast(value)
46
63
  value.unpack1('E')
47
64
  end
48
65
  end
49
66
 
50
67
  class Boolean < Base
68
+ # @param value [String]
51
69
  def type_cast(value)
52
70
  value.strip.match?(/^(y|t)$/i)
53
71
  end
54
72
  end
55
73
 
56
74
  class Date < Base
75
+ # @param value [String]
57
76
  def type_cast(value)
58
77
  value.match?(/\d{8}/) && ::Date.strptime(value, '%Y%m%d')
59
78
  rescue StandardError
@@ -62,6 +81,7 @@ module DBF
62
81
  end
63
82
 
64
83
  class DateTime < Base
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,7 @@ module DBF
72
92
  end
73
93
 
74
94
  class Memo < Base
95
+ # @param value [String]
75
96
  def type_cast(value)
76
97
  if encoding && !value.nil?
77
98
  value.force_encoding(@encoding).encode(Encoding.default_external, undef: :replace, invalid: :replace)
@@ -82,12 +103,14 @@ module DBF
82
103
  end
83
104
 
84
105
  class General < Base
106
+ # @param value [String]
85
107
  def type_cast(value)
86
108
  value
87
109
  end
88
110
  end
89
111
 
90
112
  class String < Base
113
+ # @param value [String]
91
114
  def type_cast(value)
92
115
  value = value.strip
93
116
  @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,9 @@ 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
17
+ DBASE7_HEADER_SIZE = 68
16
18
 
17
19
  VERSIONS = {
18
20
  '02' => 'FoxBase',
@@ -21,6 +23,7 @@ module DBF
21
23
  '05' => 'dBase V without memo file',
22
24
  '07' => 'Visual Objects 1.x',
23
25
  '30' => 'Visual FoxPro',
26
+ '32' => 'Visual FoxPro with field type Varchar or Varbinary',
24
27
  '31' => 'Visual FoxPro with AutoIncrement field',
25
28
  '43' => 'dBASE IV SQL table files, no memo',
26
29
  '63' => 'dBASE IV SQL system files, no memo',
@@ -28,6 +31,7 @@ module DBF
28
31
  '83' => 'dBase III with memo file',
29
32
  '87' => 'Visual Objects 1.x with memo file',
30
33
  '8b' => 'dBase IV with memo file',
34
+ '8c' => 'dBase 7',
31
35
  '8e' => 'dBase IV with SQL table',
32
36
  'cb' => 'dBASE IV SQL table files, with memo',
33
37
  'f5' => 'FoxPro with memo file',
@@ -67,9 +71,9 @@ module DBF
67
71
  # table = DBF::Table.new 'data.dbf', nil, 'cp437'
68
72
  # table = DBF::Table.new 'data.dbf', 'memo.dbt', Encoding::US_ASCII
69
73
  #
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
74
+ # @param data [String, StringIO] data Path to the dbf file or a StringIO object
75
+ # @param memo [optional String, StringIO] memo Path to the memo file or a StringIO object
76
+ # @param encoding [optional String, Encoding] encoding Name of the encoding or an Encoding object
73
77
  def initialize(data, memo = nil, encoding = nil)
74
78
  @data = open_data(data)
75
79
  @encoding = encoding || header.encoding
@@ -98,7 +102,7 @@ module DBF
98
102
  #
99
103
  # @return [String]
100
104
  def column_names
101
- columns.map(&:name)
105
+ @column_names ||= columns.map(&:name)
102
106
  end
103
107
 
104
108
  # All columns
@@ -145,8 +149,8 @@ module DBF
145
149
  # returned. The equivalent SQL would be "WHERE key1 = 'value1'
146
150
  # AND key2 = 'value2'".
147
151
  #
148
- # @param [Integer, Symbol] command
149
- # @param [optional, Hash] options Hash of search parameters
152
+ # @param command [Integer, Symbol] command
153
+ # @param options [optional, Hash] options Hash of search parameters
150
154
  # @yield [optional, DBF::Record, NilClass]
151
155
  def find(command, options = {}, &block)
152
156
  case command
@@ -211,16 +215,35 @@ module DBF
211
215
 
212
216
  def build_columns # :nodoc:
213
217
  safe_seek do
214
- @data.seek(DBF_HEADER_SIZE)
218
+ @data.seek(header_size)
215
219
  [].tap do |columns|
216
220
  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'))
221
+ args = case version
222
+ when '02'
223
+ [self, *@data.read(header_size * 2).unpack('A11 a C'), 0]
224
+ when '8c'
225
+ [self, *@data.read(48).unpack('A32 a C C x13')]
226
+ else
227
+ [self, *@data.read(header_size).unpack('A11 a x4 C2')]
228
+ end
229
+
230
+ columns << Column.new(*args)
219
231
  end
220
232
  end
221
233
  end
222
234
  end
223
235
 
236
+ def header_size
237
+ case version
238
+ when '02'
239
+ DBASE2_HEADER_SIZE
240
+ when '8c'
241
+ DBASE7_HEADER_SIZE
242
+ else
243
+ DBASE3_HEADER_SIZE
244
+ end
245
+ end
246
+
224
247
  def deleted_record? # :nodoc:
225
248
  flag = @data.read(1)
226
249
  flag ? flag.unpack1('a') == '*' : true
@@ -250,7 +273,7 @@ module DBF
250
273
  def header # :nodoc:
251
274
  @header ||= safe_seek do
252
275
  @data.seek(0)
253
- Header.new(@data.read(DBF_HEADER_SIZE))
276
+ Header.new(@data.read(DBASE3_HEADER_SIZE))
254
277
  end
255
278
  end
256
279