iron-import 0.8.2 → 0.8.3

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: ce84c9899f23abe7fd1f3ed06dc3f50e8986a585
4
- data.tar.gz: e1cd18c73aa8663bd0b98305dd18b0ddaee9f75e
3
+ metadata.gz: 2d9d43cccc1da84c981d29256f46c9c225241ba9
4
+ data.tar.gz: df64d0839daf658b286d0513c404271899303a40
5
5
  SHA512:
6
- metadata.gz: ca04f44c1434825045fb27b1d2aa0a2b16b70174dc43bf5f2aa89212331dc06e06ef41d1e4f0070971325f9014d0b6ed4e17a20cf47cb137c2312334255690ee
7
- data.tar.gz: 824b6842a4a2c60c1d1ee8737b490fe96e8029e3f8042fa548b85927d8735d2c25c551b73fbc769e407294cdbcc663ec888477746833cb8296c9ffa5ffcaf928
6
+ metadata.gz: 23e16558e41b48c89d45c11e2fc38f1efebdd56dece3866a691c96c98d92371c7cae8e1ad0e874217d81ec649d906b71fac057208cd2c09e49cd47312a83b3ee
7
+ data.tar.gz: 9601f72f3b8c651b68c60440ee4ef5838c075daaa6a84146600ff876da2eeb2e1c5eb8bd4e9ce4e36aca689a8068afe486b941c0f807d7300e0d21e164ad62b0
data/History.txt CHANGED
@@ -1,16 +1,26 @@
1
+ == 0.8.3 / 2017-08-22
2
+
3
+ * Add :bool column type (supports 'yes'/'no', 'Y'/'N', 0/1, 'true'/'false', 'T'/'F')
4
+ * Add error when failing to find valid header due to Importer#validate_columns failure
5
+ * Utilize new Class#inspect_only feature to make #inspect worth a damn on Column & Row during debugging
6
+ * Execute Importer#validate_columns in importer context to allow explicit #add_error calls
7
+
1
8
  == 0.8.2 / 2017-08-01
9
+
2
10
  * Add Importer#on_success to enable conditional processing once an import has been successfully completed
3
11
  * Pre-parse values with Column#type set when using Column#parse (instead of ignoring it)
4
12
  * Make importer scope available in Importer#process and the block form of Importer#import
5
13
  * Add backtrace info to error logging for exceptions to help during debugging
6
14
 
7
15
  == 0.8.1 / 2017-07-18
16
+
8
17
  * Do not include optional headers in #missing_headers
9
18
  * Improve string parsing to strip trailing '.0' from incoming float values
10
19
  * Add #to_h to Row for consistency with Column
11
20
  * Bugfix for calls to #add_error with invalid calling signature
12
21
 
13
22
  == 0.8.0 / 2017-06-29
23
+
14
24
  * Breaking Change: change signature of Importer#add_error to support new features
15
25
  * Breaking Change: Importer.missing_headers will be [] instead of nil on all headers found
16
26
  * Breaking Change: remove deprecated method Column#required!
data/Version.txt CHANGED
@@ -1 +1 @@
1
- 0.8.2
1
+ 0.8.3
@@ -22,7 +22,7 @@ class Importer
22
22
  # header /(price|cost)/i
23
23
  #
24
24
  # # Tells the data parser what type of data this column contains, one
25
- # # of :integer, :string, :date, :float, or :cents. Defaults to :string.
25
+ # # of :integer, :string, :date, :float, :bool or :cents. Defaults to :string.
26
26
  # type :cents
27
27
  #
28
28
  # # Instead of a type, you can set an explicit parse block. Be aware
@@ -30,8 +30,8 @@ class Importer
30
30
  # # seems like the "same" source value, for example an Excel source file
31
31
  # # will give you a float value for all numeric types, even "integers", while
32
32
  # # CSV and HTML values are always strings. By default, will take the raw
33
- # # value of the row, but if used with #type, you can have the pre-processing
34
- # # of the type as your input.
33
+ # # value of the row, but if used with #type, you can have the pre-processed
34
+ # # output of that type as your input.
35
35
  # parse do |raw_value|
36
36
  # val = raw_value.to_i + 1000
37
37
  # # NOTE: we're in a block, so don't do this:
@@ -53,9 +53,9 @@ class Importer
53
53
  # virtual!
54
54
  #
55
55
  # # When #virtual! is set, gets called to calculate each row's value for this
56
- # # column using the row's parsed values.
56
+ # # column using the row's parsed values from other columns.
57
57
  # calculate do |row|
58
- # row[:some_col] + 5
58
+ # row[:other_col_key] + 5
59
59
  # end
60
60
  # end
61
61
  # end
@@ -83,6 +83,9 @@ class Importer
83
83
  dsl_accessor :header, :position, :type
84
84
  dsl_accessor :parse, :validate, :calculate
85
85
  dsl_flag :optional, :virtual
86
+
87
+ # Limit our inspect to avoid dumping whole importer
88
+ inspect_only :key, :type, :optional, :virtual, :position, :index
86
89
 
87
90
  def self.pos_to_index(pos)
88
91
  raise 'Invalid column position: ' + pos.inspect unless pos.is_a?(String) && pos.match(/\A[a-z]{1,3}\z/i)
@@ -283,7 +283,17 @@ class Importer
283
283
  # Pull out the date part of the string and convert
284
284
  date_str = val.to_s.extract(/[0-9]+[\-\/][0-9]+[\-\/][0-9]+/)
285
285
  date_str.to_date rescue nil
286
-
286
+
287
+ when :bool then
288
+ val_str = parse_value(val, :string).to_s.downcase
289
+ if ['true','yes','y','t','1'].include?(val_str)
290
+ return true
291
+ elsif ['false','no','n','f','0'].include?(val_str)
292
+ return false
293
+ else
294
+ nil
295
+ end
296
+
287
297
  else
288
298
  raise "Unknown column type #{type.inspect} - unimplemented?"
289
299
  end
@@ -109,7 +109,7 @@ class Importer
109
109
  dsl_flag :headerless
110
110
  # Explicitly sets the row number (1-indexed) where data rows begin,
111
111
  # usually left defaulted to nil to automatically start after the header
112
- # row.
112
+ # row, or on the first row if #headerless! is set.
113
113
  dsl_accessor :start_row
114
114
  # Set to a block/lambda taking a parsed but unvalidated row as a hash,
115
115
  # return true to keep, false to skip.
@@ -363,6 +363,7 @@ class Importer
363
363
  end
364
364
 
365
365
  # Read in the data!
366
+ loaded = false
366
367
  @reader.load(path_or_stream, scopes) do |raw_rows|
367
368
  # Find our column layout, start of data, etc
368
369
  if find_header(raw_rows)
@@ -374,6 +375,7 @@ class Importer
374
375
  end
375
376
  end
376
377
  # We've found a workable sheet/table/whatever, stop looking
378
+ loaded = true
377
379
  true
378
380
 
379
381
  else
@@ -383,9 +385,14 @@ class Importer
383
385
  end
384
386
  end
385
387
 
386
- # If we have any missing headers, note that fact
387
- if @missing_headers && @missing_headers.count > 0
388
- add_error("Unable to locate required column header for column(s): " + @missing_headers.collect{|c| ":#{c}"}.list_join(', '))
388
+ # Verify that we found a working set of rows
389
+ unless loaded
390
+ # If we have any missing headers, note that fact
391
+ if @missing_headers && @missing_headers.count > 0
392
+ add_error("Unable to locate required column header for column(s): " + @missing_headers.collect{|c| ":#{c}"}.list_join(', '))
393
+ else
394
+ add_error("Unable to locate required column headers!")
395
+ end
389
396
  end
390
397
 
391
398
  # If we're here with no errors, we rule!
@@ -533,15 +540,18 @@ class Importer
533
540
  col.data.index = nil
534
541
  col.data.header_text = nil
535
542
  end
536
-
543
+
537
544
  # Have we found them all, or at least a valid sub-set?
538
545
  header_found = remaining.empty?
539
546
  unless header_found
540
- if remaining.all?(&:optional?)
547
+ if found_columns.any? && remaining.all?(&:optional?)
541
548
  if @column_validator
542
549
  # Run custom column validator
543
- cols = found_columns
544
- header_found = @column_validator.call(cols)
550
+ valid = false
551
+ had_error = Error.with_context(@importer, nil, nil, nil) do
552
+ valid = DslProxy.exec(self, found_columns, &@column_validator)
553
+ end
554
+ header_found = !had_error && valid
545
555
  else
546
556
  # No validator... do we have any found columns at all???
547
557
  header_found = @columns.any?(&:present?)
@@ -2,8 +2,12 @@ class Importer
2
2
 
3
3
  class Row
4
4
 
5
+ # Attributes
5
6
  attr_reader :line, :values, :errors
6
7
 
8
+ # Limit our inspect to avoid dumping whole importer
9
+ inspect_only :line, :values
10
+
7
11
  def initialize(importer, line, value_hash = nil)
8
12
  @importer = importer
9
13
  @line = line
@@ -28,4 +28,75 @@ describe Importer::CsvReader do
28
28
  ]
29
29
  end
30
30
 
31
+ it 'should fail on WSM sample data' do
32
+ importer = Importer.build do
33
+ column :company_name do
34
+ optional!
35
+ end
36
+ virtual_column :company do
37
+ calculate do |row|
38
+ if column(:company_name).present?
39
+ row[:company_name]
40
+ else
41
+ [row[:store_code], row[:store_num]].list_join(', ')
42
+ end
43
+ end
44
+ end
45
+ column :store_code do
46
+ header /code$/i
47
+ optional!
48
+ end
49
+ column :store_num do
50
+ optional!
51
+ header /num(ber)?$/i
52
+ end
53
+ virtual_column :store do
54
+ calculate do |row|
55
+ Store.find_by_upc(row[:upc])
56
+ end
57
+ end
58
+ column :buyer_name do
59
+ optional!
60
+ end
61
+ column :buyer_email do
62
+ optional!
63
+ header /buyer\s*email/i
64
+ validate do |val|
65
+ val.match? /^\s*([a-z0-9_\-\+\.]+@[a-z0-9\.\-]+\.[a-z]+)(,\s*[a-z0-9_\-\+\.]+@[a-z0-9\.\-]+\.[a-z]+)*\s*$/i
66
+ end
67
+ end
68
+ column :rep_email do
69
+ optional!
70
+ header /^(sales\s*)?rep\s*email/i
71
+ validate do |val|
72
+ val.match? /^\s*([a-z0-9_\-\+\.]+@[a-z0-9\.\-]+\.[a-z]+)(,\s*[a-z0-9_\-\+\.]+@[a-z0-9\.\-]+\.[a-z]+)*\s*$/i
73
+ end
74
+ end
75
+ column :regional do
76
+ optional!
77
+ type :bool
78
+ end
79
+
80
+ # We need a company name column if none passed in
81
+ validate_columns do |cols|
82
+ keys = cols.collect(&:key)
83
+ has_company = keys.include?(:company_name)
84
+ has_company && (keys.include?(:store_num) || keys.include?(:store_code))
85
+ end
86
+
87
+ # Only pay attention to rows with a store num or code
88
+ # filter_rows do |row|
89
+ # row[:store_num].present? || row[:store_code].present?
90
+ # end
91
+
92
+ # Make sure rows are valid
93
+ validate_rows do |row|
94
+ add_error("Unable to locate specified company") unless row[:company].present?
95
+ add_error("Unable to locate specified store") unless row[:store].present?
96
+ end
97
+ end
98
+ importer.import(SpecHelper.sample_path('wsm-data.csv')).should be_false
99
+ importer.errors.first.to_s.should == "Unable to locate required column headers!"
100
+ end
101
+
31
102
  end
@@ -44,7 +44,8 @@ describe Importer::DataReader do
44
44
  '' => nil,
45
45
  255 => '255',
46
46
  -1.5 => '-1.5',
47
- 10.0 => '10'
47
+ 10.0 => '10',
48
+ '10.0' => '10.0'
48
49
  }.each_pair do |val, res|
49
50
  @reader.parse_value(val, :string).should === res
50
51
  end
@@ -60,7 +61,9 @@ describe Importer::DataReader do
60
61
  '-95' => -9500,
61
62
  52 => 5200,
62
63
  1.0 => 100,
63
- 1.25 => 125
64
+ 1.25 => 125,
65
+ -2.001 => -200,
66
+ 'bob' => nil
64
67
  }.each_pair do |val, res|
65
68
  @reader.parse_value(val, :cents).should === res
66
69
  end
@@ -79,6 +82,29 @@ describe Importer::DataReader do
79
82
  end
80
83
  end
81
84
 
85
+ it 'should parse bools' do
86
+ {
87
+ 'tRue' => true,
88
+ 'yes ' => true,
89
+ 'T' => true,
90
+ 'y' => true,
91
+ '1' => true,
92
+ 1.0 => true,
93
+ 'FALSE' => false,
94
+ 'no' => false,
95
+ 'F' => false,
96
+ 'n' => false,
97
+ '0' => false,
98
+ 0 => false,
99
+ 'tim' => nil,
100
+ nil => nil,
101
+ '' => nil,
102
+ 'xyz' => nil
103
+ }.each_pair do |val, res|
104
+ @reader.parse_value(val, :bool).should === res
105
+ end
106
+ end
107
+
82
108
  it 'should build an instance based on format' do
83
109
  Importer::DataReader.for_format(@importer, :csv).should be_a(Importer::CsvReader)
84
110
  Importer::DataReader.for_format(@importer, :xls).should be_a(Importer::XlsReader)
@@ -0,0 +1,3 @@
1
+ ID,Number,Code,Name,Region,,Buyer Name,Buyer Emails,Sales Rep Emails,,,,,,
2
+ 4293,10194,BHL,Bayhill,FL,BHL,,"example1@wholefoods.com, example3@wholefoods.com",,,,,,,
3
+ 4294,10096,BIS,Biscayne,FL,BIS,,"example2@wholefoods.com, example4@wholefoods.com",,,,,,,
data/spec/spec_helper.rb CHANGED
@@ -10,6 +10,11 @@ RSpec.configure do |config|
10
10
  config.color = true
11
11
  config.add_formatter 'documentation'
12
12
  config.backtrace_exclusion_patterns = [/rspec/]
13
+ # Allow us to use: it '...', :focus do ... end
14
+ # rather than needing: it '...', :focus => true do ... end
15
+ config.treat_symbols_as_metadata_keys_with_true_values = true
16
+ # If everything is filtered, run everything - used if no :focus element is present
17
+ config.run_all_when_everything_filtered = true
13
18
  end
14
19
 
15
20
  module SpecHelper
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iron-import
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Morris
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-01 00:00:00.000000000 Z
11
+ date: 2017-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: iron-extensions
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '1.2'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 1.2.1
22
+ version: 1.2.2
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '1.2'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 1.2.1
32
+ version: 1.2.2
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: iron-dsl
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -133,6 +133,7 @@ files:
133
133
  - spec/samples/simple.csv
134
134
  - spec/samples/simple.html
135
135
  - spec/samples/test-products.xls
136
+ - spec/samples/wsm-data.csv
136
137
  - spec/spec_helper.rb
137
138
  homepage: http://irongaze.com
138
139
  licenses: