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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +166 -104
- data/Gemfile +1 -0
- data/Gemfile.lock +63 -52
- data/LICENSE +1 -1
- data/README.md +110 -16
- data/bin/dbf +2 -0
- data/docs/CNAME +1 -0
- data/docs/DBF.html +200 -0
- data/docs/DBF/Column.html +947 -0
- data/docs/DBF/Column/LengthError.html +124 -0
- data/docs/DBF/Column/NameError.html +124 -0
- data/docs/DBF/ColumnType.html +115 -0
- data/docs/DBF/ColumnType/Base.html +389 -0
- data/docs/DBF/ColumnType/Boolean.html +238 -0
- data/docs/DBF/ColumnType/Currency.html +238 -0
- data/docs/DBF/ColumnType/Date.html +242 -0
- data/docs/DBF/ColumnType/DateTime.html +246 -0
- data/docs/DBF/ColumnType/Double.html +238 -0
- data/docs/DBF/ColumnType/Float.html +238 -0
- data/docs/DBF/ColumnType/General.html +238 -0
- data/docs/DBF/ColumnType/Memo.html +246 -0
- data/docs/DBF/ColumnType/Nil.html +238 -0
- data/docs/DBF/ColumnType/Number.html +242 -0
- data/docs/DBF/ColumnType/SignedLong.html +238 -0
- data/docs/DBF/ColumnType/String.html +240 -0
- data/docs/DBF/Database.html +126 -0
- data/docs/DBF/Database/Foxpro.html +653 -0
- data/docs/DBF/Database/Table.html +346 -0
- data/docs/DBF/FileNotFoundError.html +124 -0
- data/docs/DBF/Header.html +723 -0
- data/docs/DBF/Memo.html +117 -0
- data/docs/DBF/Memo/Base.html +485 -0
- data/docs/DBF/Memo/Dbase3.html +242 -0
- data/docs/DBF/Memo/Dbase4.html +230 -0
- data/docs/DBF/Memo/Foxpro.html +268 -0
- data/docs/DBF/NoColumnsDefined.html +124 -0
- data/docs/DBF/Record.html +773 -0
- data/docs/DBF/Schema.html +980 -0
- data/docs/DBF/Table.html +1571 -0
- data/docs/_index.html +415 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +497 -0
- data/docs/file.README.html +359 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +359 -0
- data/docs/js/app.js +314 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +675 -0
- data/docs/top-level-namespace.html +110 -0
- data/lib/dbf/column.rb +8 -7
- data/lib/dbf/column_type.rb +23 -0
- data/lib/dbf/database/foxpro.rb +7 -2
- data/lib/dbf/header.rb +11 -3
- data/lib/dbf/record.rb +5 -5
- data/lib/dbf/schema.rb +4 -3
- data/lib/dbf/table.rb +34 -11
- data/lib/dbf/version.rb +1 -1
- data/spec/dbf/column_spec.rb +3 -3
- data/spec/dbf/file_formats_spec.rb +54 -0
- data/spec/fixtures/dbase_02.dbf +0 -0
- data/spec/fixtures/dbase_02_summary.txt +23 -0
- data/spec/fixtures/dbase_32.dbf +0 -0
- data/spec/fixtures/dbase_32_summary.txt +11 -0
- data/spec/fixtures/dbase_8c.dbf +0 -0
- metadata +56 -6
- data/docs/supported_encodings.csv +0 -60
- 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
|
+
— 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> »
|
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]
|
32
|
-
# @param [String]
|
33
|
-
# @param [
|
34
|
-
# @param [Integer]
|
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
|
-
|
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:
|
data/lib/dbf/column_type.rb
CHANGED
@@ -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
|
data/lib/dbf/database/foxpro.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
# @
|
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
|
-
|
13
|
-
@encoding = DBF::ENCODINGS[@encoding_key]
|
12
|
+
unpack_header
|
14
13
|
end
|
15
14
|
|
16
15
|
def unpack_header
|
17
|
-
@data.unpack('H2
|
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
|
-
# @
|
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
|
-
|
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(
|
218
|
+
@data.seek(header_size)
|
215
219
|
[].tap do |columns|
|
216
220
|
until end_of_record?
|
217
|
-
|
218
|
-
|
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(
|
276
|
+
Header.new(@data.read(DBASE3_HEADER_SIZE))
|
254
277
|
end
|
255
278
|
end
|
256
279
|
|