iron-import 0.8.2 → 0.8.3

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