dbf 2.0.10 → 2.0.11

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: 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