dbf 2.0.10 → 2.0.11

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: 0c8808826478c973095384cbe5b0f86e94f96ed8
4
- data.tar.gz: 0ccf185436e3753f2bc08cb7211d1dea19e4b438
3
+ metadata.gz: 8aee45603d7f53ed73d7a1a6068f823330c1770a
4
+ data.tar.gz: 1478ad383f4b9b568d27ea315f16ac73690e3962
5
5
  SHA512:
6
- metadata.gz: 9cda0f53ddb72957e83d63504b0d0a58227b05f07623b057cacce2d5325203ebb3419ce8524a82d2ef59b5d5a2dd5a35edcc5f4f5e9cc1bee7e3186b2565a0ae
7
- data.tar.gz: 965eaaf92b0274c7e88b88e5c31c02387f95dd9810b25a65b2e41c756e615850e1ad6778265e6aa343b4c189caa040e5610672419337a874693f9cdf3c296af5
6
+ metadata.gz: 1d2bdc66cdfc536d6f0f1881dd0bf26f883521357b775ca847ffff09f1b8d7fa0f7e8a1fcc78cef4bb97067e57969f9730c9bb2de87b1c67d9e984184899f87b
7
+ data.tar.gz: 055e8f333772efc02e22556a225094cb2e1368f77cffac249c6b660fd2304c0ca5b838c86cb2994a3408eb03313bb253e686610acabe15586e23fa304a4a83e4
@@ -1,3 +1,7 @@
1
+ # 2.0.11
2
+ - Foxpro doubles should always return the full stored precision
3
+ (see https://github.com/infused/dbf/pull/69)
4
+
1
5
  # 2.0.10
2
6
  - allow 0 length fields, but always return nil as value
3
7
 
@@ -1,22 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dbf (2.0.9)
4
+ dbf (2.0.10)
5
5
  fastercsv (~> 1.5)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- celluloid (0.16.0)
11
- timers (~> 4.0.0)
12
10
  coderay (1.1.0)
13
11
  diff-lcs (1.2.5)
14
12
  fastercsv (1.5.5)
15
- ffi (1.9.8)
13
+ ffi (1.9.10)
14
+ ffi (1.9.10-java)
16
15
  formatador (0.2.5)
17
- guard (2.12.5)
16
+ guard (2.13.0)
18
17
  formatador (>= 0.2.4)
19
- listen (~> 2.7)
18
+ listen (>= 2.7, <= 4.0)
20
19
  lumberjack (~> 1.0)
21
20
  nenv (~> 0.1)
22
21
  notiffany (~> 0.0)
@@ -24,48 +23,52 @@ GEM
24
23
  shellany (~> 0.0)
25
24
  thor (>= 0.18.1)
26
25
  guard-compat (1.2.1)
27
- guard-rspec (4.5.0)
26
+ guard-rspec (4.6.4)
28
27
  guard (~> 2.1)
29
28
  guard-compat (~> 1.1)
30
29
  rspec (>= 2.99.0, < 4.0)
31
- hitimes (1.2.2)
32
- listen (2.10.0)
33
- celluloid (~> 0.16.0)
30
+ listen (3.0.3)
34
31
  rb-fsevent (>= 0.9.3)
35
32
  rb-inotify (>= 0.9)
36
33
  lumberjack (1.0.9)
37
34
  method_source (0.8.2)
38
35
  nenv (0.2.0)
39
- notiffany (0.0.6)
36
+ notiffany (0.0.7)
40
37
  nenv (~> 0.1)
41
38
  shellany (~> 0.0)
42
39
  pry (0.10.1)
43
40
  coderay (~> 1.1.0)
44
41
  method_source (~> 0.8.1)
45
42
  slop (~> 3.4)
46
- rb-fsevent (0.9.4)
43
+ pry (0.10.1-java)
44
+ coderay (~> 1.1.0)
45
+ method_source (~> 0.8.1)
46
+ slop (~> 3.4)
47
+ spoon (~> 0.0)
48
+ rb-fsevent (0.9.5)
47
49
  rb-inotify (0.9.5)
48
50
  ffi (>= 0.5.0)
49
- rspec (3.2.0)
50
- rspec-core (~> 3.2.0)
51
- rspec-expectations (~> 3.2.0)
52
- rspec-mocks (~> 3.2.0)
53
- rspec-core (3.2.3)
54
- rspec-support (~> 3.2.0)
55
- rspec-expectations (3.2.1)
51
+ rspec (3.3.0)
52
+ rspec-core (~> 3.3.0)
53
+ rspec-expectations (~> 3.3.0)
54
+ rspec-mocks (~> 3.3.0)
55
+ rspec-core (3.3.2)
56
+ rspec-support (~> 3.3.0)
57
+ rspec-expectations (3.3.1)
56
58
  diff-lcs (>= 1.2.0, < 2.0)
57
- rspec-support (~> 3.2.0)
58
- rspec-mocks (3.2.1)
59
+ rspec-support (~> 3.3.0)
60
+ rspec-mocks (3.3.2)
59
61
  diff-lcs (>= 1.2.0, < 2.0)
60
- rspec-support (~> 3.2.0)
61
- rspec-support (3.2.2)
62
+ rspec-support (~> 3.3.0)
63
+ rspec-support (3.3.0)
62
64
  shellany (0.0.1)
63
65
  slop (3.6.0)
66
+ spoon (0.0.4)
67
+ ffi
64
68
  thor (0.19.1)
65
- timers (4.0.1)
66
- hitimes
67
69
 
68
70
  PLATFORMS
71
+ java
69
72
  ruby
70
73
 
71
74
  DEPENDENCIES
@@ -24,13 +24,8 @@ module DBF
24
24
  @version = table.version
25
25
  @encoding = table.encoding
26
26
 
27
- unless length >= 0
28
- raise LengthError, 'field length must be 0 or greater'
29
- end
30
-
31
- if @name.empty?
32
- raise NameError, 'column name cannot be empty'
33
- end
27
+ validate_length
28
+ validate_name
34
29
  end
35
30
 
36
31
  # Cast value to native type
@@ -40,17 +35,8 @@ module DBF
40
35
  def type_cast(value)
41
36
  return nil if length == 0
42
37
 
43
- case type
44
- when 'N' then unpack_number(value)
45
- when 'I' then unpack_unsigned_long(value)
46
- when 'F' then value.to_f
47
- when 'Y' then (unpack_unsigned_long(value) / 10000.0).to_f
48
- when 'D' then decode_date(value)
49
- when 'T' then decode_datetime(value)
50
- when 'L' then boolean(value)
51
- when 'M' then decode_memo(value)
52
- else encode_string(value.to_s).strip
53
- end
38
+ meth = type_cast_methods[type]
39
+ meth ? send(meth, value) : encode_string(value, true)
54
40
  end
55
41
 
56
42
  # Returns true if the column is a memo
@@ -75,95 +61,135 @@ module DBF
75
61
  # @return [String]
76
62
  def underscored_name
77
63
  @underscored_name ||= begin
78
- name.gsub(/::/, '/').
79
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
80
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
81
- tr('-', '_').
82
- downcase
64
+ un = name.dup
65
+ un.gsub!(/::/, '/')
66
+ un.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
67
+ un.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
68
+ un.tr!('-', '_')
69
+ un.downcase!
70
+ un
83
71
  end
84
72
  end
85
73
 
86
74
  private
87
75
 
88
- def decode_date(value) #nodoc
76
+ def type_cast_methods # nodoc
77
+ {
78
+ 'N' => :unpack_number,
79
+ 'I' => :unpack_unsigned_long,
80
+ 'F' => :unpack_float,
81
+ 'Y' => :unpack_currency,
82
+ 'D' => :decode_date,
83
+ 'T' => :decode_datetime,
84
+ 'L' => :boolean,
85
+ 'M' => :decode_memo,
86
+ 'B' => :unpack_double
87
+ }
88
+ end
89
+
90
+ def decode_date(value) # nodoc
89
91
  value.gsub!(' ', '0')
90
92
  value !~ /\S/ ? nil : Date.parse(value)
91
93
  rescue
92
94
  nil
93
95
  end
94
96
 
95
- def decode_datetime(value) #nodoc
97
+ def decode_datetime(value) # nodoc
96
98
  days, msecs = value.unpack('l2')
97
99
  secs = (msecs / 1000).to_i
98
- DateTime.jd(days, (secs/3600).to_i, (secs/60).to_i % 60, secs % 60)
100
+ DateTime.jd(days, (secs / 3600).to_i, (secs / 60).to_i % 60, secs % 60)
99
101
  rescue
100
102
  nil
101
103
  end
102
104
 
103
- def decode_memo(value) #nodoc
105
+ def decode_memo(value) # nodoc
104
106
  value && encode_string(value)
105
107
  end
106
108
 
107
- def unpack_number(value) #nodoc
109
+ def unpack_number(value) # nodoc
108
110
  decimal.zero? ? value.to_i : value.to_f
109
111
  end
110
112
 
111
- def unpack_unsigned_long(value) #nodoc
113
+ def unpack_currency(value) # nodoc
114
+ (unpack_unsigned_long(value) / 10_000.0).to_f
115
+ end
116
+
117
+ def unpack_unsigned_long(value) # nodoc
112
118
  value.unpack('V')[0]
113
119
  end
114
120
 
115
- def boolean(value) #nodoc
121
+ def unpack_float(value) # nodoc
122
+ value.to_f
123
+ end
124
+
125
+ def unpack_double(value) # nodoc
126
+ value.unpack('E')[0]
127
+ end
128
+
129
+ def boolean(value) # nodoc
116
130
  value.strip =~ /^(y|t)$/i ? true : false
117
131
  end
118
132
 
119
- def encode_string(value) #nodoc
120
- if @encoding && table.supports_encoding?
121
- if table.supports_string_encoding?
122
- value.force_encoding(@encoding).encode(*encoding_args)
123
- elsif table.supports_iconv?
124
- Iconv.conv('UTF-8', @encoding, value)
133
+ def encode_string(value, strip_output = false) # nodoc
134
+ output =
135
+ if supports_encoding? && table.supports_string_encoding?
136
+ value.to_s.force_encoding(@encoding).encode(*encoding_args)
137
+ elsif supports_encoding? && table.supports_iconv?
138
+ Iconv.conv('UTF-8', @encoding, value.to_s)
139
+ else
140
+ value
125
141
  end
126
- else
127
- value
128
- end
142
+
143
+ strip_output ? output.strip : output
129
144
  end
130
145
 
131
- def encoding_args #nodoc
146
+ def encoding_args # nodoc
132
147
  [Encoding.default_external, {:undef => :replace, :invalid => :replace}]
133
148
  end
134
149
 
135
- def schema_data_type #nodoc
150
+ def schema_data_type # nodoc
136
151
  case type
137
- when "N", "F"
138
- decimal > 0 ? ":float" : ":integer"
139
- when "I"
140
- ":integer"
141
- when "Y"
142
- ":decimal, :precision => 15, :scale => 4"
143
- when "D"
144
- ":date"
145
- when "T"
146
- ":datetime"
147
- when "L"
148
- ":boolean"
149
- when "M"
150
- ":text"
151
- when "B"
152
+ when 'N', 'F'
153
+ decimal > 0 ? ':float' : ':integer'
154
+ when 'I'
155
+ ':integer'
156
+ when 'Y'
157
+ ':decimal, :precision => 15, :scale => 4'
158
+ when 'D'
159
+ ':date'
160
+ when 'T'
161
+ ':datetime'
162
+ when 'L'
163
+ ':boolean'
164
+ when 'M'
165
+ ':text'
166
+ when 'B'
152
167
  if DBF::Table::FOXPRO_VERSIONS.keys.include?(@version)
153
- decimal > 0 ? ":float" : ":integer"
168
+ ':float'
154
169
  else
155
- ":text"
170
+ ':text'
156
171
  end
157
172
  else
158
173
  ":string, :limit => #{length}"
159
174
  end
160
175
  end
161
176
 
162
- def clean(value) #nodoc
177
+ def clean(value) # nodoc
163
178
  truncated_value = value.strip.partition("\x00").first
164
179
  truncated_value.gsub(/[^\x20-\x7E]/, '')
165
180
  end
166
181
 
182
+ def validate_length
183
+ raise LengthError, 'field length must be 0 or greater' if length < 0
184
+ end
185
+
186
+ def validate_name
187
+ raise NameError, 'column name cannot be empty' if @name.empty?
188
+ end
189
+
190
+ def supports_encoding?
191
+ @encoding && table.supports_encoding?
192
+ end
167
193
  end
168
194
  end
169
195
  end
@@ -1,7 +1,7 @@
1
1
  module DBF
2
2
  module Column
3
3
  class Foxpro < Base
4
- # def unpack_binary(value) #nodoc
4
+ # def unpack_binary(value) # nodoc
5
5
  # value.unpack('d')[0]
6
6
  # end
7
7
  end
@@ -1,12 +1,13 @@
1
1
  module DBF
2
- # DBF::Database::Foxpro is the primary interface to a Visual Foxpro database container (.dbc file).
3
- # When using this database container, long fieldnames are supported, and you can reference tables
4
- # directly instead of instantiating Table objects yourself.
5
- # Table references are created based on the filename, but it this class tries to correct the
6
- # table filenames because they could be wrong for case sensitive filesystems, e.g. when
7
- # a foxpro database is uploaded to a linux server.
2
+ # DBF::Database::Foxpro is the primary interface to a Visual Foxpro database
3
+ # container (.dbc file). When using this database container, long fieldnames
4
+ # are supported, and you can reference tables directly instead of
5
+ # instantiating Table objects yourself.
6
+ # Table references are created based on the filename, but it this class
7
+ # tries to correct the table filenames because they could be wrong for
8
+ # case sensitive filesystems, e.g. when a foxpro database is uploaded to
9
+ # a linux server.
8
10
  module Database
9
-
10
11
  class Foxpro
11
12
  # Opens a DBF::Database::Foxpro
12
13
  # Examples:
@@ -16,15 +17,13 @@ module DBF
16
17
  # # Calling a table
17
18
  # contacts = db.contacts.record(0)
18
19
  def initialize(path)
19
- begin
20
- @path = path
21
- @dirname = File.dirname(@path)
22
- @db = DBF::Table.new(@path)
23
- @tables = extract_dbc_data
24
-
25
- rescue Errno::ENOENT
26
- raise DBF::FileNotFoundError.new("file not found: #{data}")
27
- end
20
+ @path = path
21
+ @dirname = File.dirname(@path)
22
+ @db = DBF::Table.new(@path)
23
+ @tables = extract_dbc_data
24
+
25
+ rescue Errno::ENOENT
26
+ raise DBF::FileNotFoundError, "file not found: #{data}"
28
27
  end
29
28
 
30
29
  def table_names
@@ -49,14 +48,14 @@ module DBF
49
48
  path = Dir.glob(glob).find { |match| match.downcase == example.downcase }
50
49
 
51
50
  unless path && File.exist?(path)
52
- raise DBF::FileNotFoundError.new("related table not found: #{name}")
51
+ raise DBF::FileNotFoundError, "related table not found: #{name}"
53
52
  end
54
53
 
55
54
  path
56
55
  end
57
56
 
58
57
  def method_missing(method, *args) # nodoc
59
- if index = table_names.index(method.to_s)
58
+ if table_names.index(method.to_s)
60
59
  table method.to_s
61
60
  else
62
61
  super
@@ -65,9 +64,10 @@ module DBF
65
64
 
66
65
  private
67
66
 
68
- # This method extracts the data from the database container. This is just an ordinary table
69
- # with a treelike structure. Field definitions are in the same order as in the linked tables
70
- # but only the long name is provided.
67
+ # This method extracts the data from the database container. This is
68
+ # just an ordinary table with a treelike structure. Field definitions
69
+ # are in the same order as in the linked tables but only the long name
70
+ # is provided.
71
71
  def extract_dbc_data # nodoc
72
72
  data = {}
73
73
  @db.each do |record|
@@ -76,18 +76,36 @@ module DBF
76
76
  case record.objecttype
77
77
  when 'Table'
78
78
  # This is a related table
79
- data[record.objectid] = {:name => record.objectname, :fields => []}
79
+ process_table record, data
80
80
  when 'Field'
81
81
  # This is a related field. The parentid points to the table object.
82
82
  # Create using the parentid if the parentid is still unknown.
83
- data[record.parentid] ||= {:name => "UNKNOWN", :fields => []}
84
- data[record.parentid][:fields] << record.objectname
83
+ process_field record, data
85
84
  end
86
85
  end
87
86
 
88
- Hash[data.values.map {|v| [v[:name], v[:fields]] }]
87
+ Hash[
88
+ data.values.map { |v| [v[:name], v[:fields]] }
89
+ ]
90
+ end
91
+
92
+ def process_table(record, data)
93
+ id = record.objectid
94
+ name = record.objectname
95
+ data[id] = table_field_hash(name)
89
96
  end
90
97
 
98
+ def process_field(record, data)
99
+ id = record.parentid
100
+ name = 'UNKNOWN'
101
+ field = record.objectname
102
+ data[id] ||= table_field_hash(name)
103
+ data[id][:fields] << field
104
+ end
105
+
106
+ def table_field_hash(name)
107
+ {:name => name, :fields => []}
108
+ end
91
109
  end
92
110
 
93
111
  class Table < DBF::Table
@@ -102,7 +120,6 @@ module DBF
102
120
  long_name = long_names[columns.index(column)]
103
121
  column_class.new(self, long_name, column.type, column.length, column.decimal)
104
122
  end
105
-
106
123
  end
107
124
  end
108
125
  end