stockboy 0.9.0 → 0.10.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +11 -0
  4. data/lib/stockboy/attribute.rb +15 -2
  5. data/lib/stockboy/attribute_map.rb +2 -1
  6. data/lib/stockboy/candidate_record.rb +30 -6
  7. data/lib/stockboy/job.rb +3 -3
  8. data/lib/stockboy/readers/csv.rb +20 -4
  9. data/lib/stockboy/translations/date.rb +35 -5
  10. data/lib/stockboy/translations/uk_date.rb +4 -8
  11. data/lib/stockboy/translations/us_date.rb +2 -12
  12. data/lib/stockboy/version.rb +1 -1
  13. data/spec/spec_helper.rb +1 -1
  14. data/spec/stockboy/attribute_map_spec.rb +7 -2
  15. data/spec/stockboy/attribute_spec.rb +22 -2
  16. data/spec/stockboy/candidate_record_spec.rb +16 -3
  17. data/spec/stockboy/configurator_spec.rb +4 -4
  18. data/spec/stockboy/filter_chain_spec.rb +1 -1
  19. data/spec/stockboy/filter_spec.rb +4 -4
  20. data/spec/stockboy/filters/missing_email_spec.rb +4 -4
  21. data/spec/stockboy/job_spec.rb +9 -9
  22. data/spec/stockboy/provider_repeater_spec.rb +3 -3
  23. data/spec/stockboy/provider_spec.rb +3 -3
  24. data/spec/stockboy/providers/file_spec.rb +6 -6
  25. data/spec/stockboy/providers/ftp_spec.rb +3 -3
  26. data/spec/stockboy/providers/imap_spec.rb +5 -5
  27. data/spec/stockboy/providers_spec.rb +4 -4
  28. data/spec/stockboy/readers/csv_spec.rb +13 -4
  29. data/spec/stockboy/readers/spreadsheet_spec.rb +5 -5
  30. data/spec/stockboy/readers/xml_spec.rb +4 -4
  31. data/spec/stockboy/readers_spec.rb +3 -3
  32. data/spec/stockboy/template_file_spec.rb +1 -1
  33. data/spec/stockboy/translations/date_spec.rb +1 -1
  34. data/spec/stockboy/translations/decimal_spec.rb +1 -1
  35. data/spec/stockboy/translations/integer_spec.rb +1 -1
  36. data/spec/stockboy/translations/time_spec.rb +1 -1
  37. data/spec/stockboy/translations/uk_date_spec.rb +1 -1
  38. data/spec/stockboy/translations/us_date_spec.rb +1 -1
  39. data/spec/stockboy/translations_spec.rb +3 -3
  40. data/stockboy.gemspec +1 -0
  41. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5447e324b114a19041b667593624345a3e74255e
4
- data.tar.gz: 66cfa4b6d78f3a7c8e861d4f943e024f59973e30
3
+ metadata.gz: f39685f84983ede11ee77ffe5ab39982e37ddac7
4
+ data.tar.gz: c16642a9c384a71c72def32658d8e4764ab46b14
5
5
  SHA512:
6
- metadata.gz: 8021a777554ad4978179e4b428e452cc4050b772d4a15426d20478fe542f293503dc1204bdf7038c39f108657d01fe4712d2011abe47bdbd24f8b8cb01e1cef5
7
- data.tar.gz: 6a5bdf862e2de473579833bc5596f30c5f6d938418975697370aa51083b70353f7b60a92defb909c5d8ec6f14dab169b504de2576c0bdab7dd0daaf12c55e8f5
6
+ metadata.gz: 3d7f4377f7973b9e88d91ea807c12987385e594ca9f55b1594dbdc652ad8aea3cf674e431394ed840d60b11454404975cf4d9dc77b3a8a3409a760b1668316ef
7
+ data.tar.gz: 7dd65ee93fbf9a6df91f052b5505fe13d2b4d1ae2901a612a7ccd87589f1f5662cf8154f5d7d397a4d59779c23cd4ddf4abbee5c70f78a0204130c9cd329e9f5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0 / 2015-01-23
4
+
5
+ * [FEATURE] Option to ignore unwanted attributes from output
6
+
3
7
  ## 0.9.0 / 2014-05-19
4
8
 
5
9
  * [FEATURE] Add JSON reader
data/README.md CHANGED
@@ -210,6 +210,17 @@ Translations are applied in order when given as an array. Since translators are
210
210
  designed to handle invalid data, they will catch exceptions and return a `nil`
211
211
  so it's a good idea to have default values at the end of the chain.
212
212
 
213
+ #### ignore:
214
+ If a field needs to be omitted when exporting to hash (to avoid downstream
215
+ clients clobbering existing values with nil), specify the option with a
216
+ callable object or a symbol that can be called against the output field value:
217
+
218
+ email ignore: ->(r) { not r.email.include?("@") }
219
+ score ignore: :zero?
220
+
221
+ The callable object receives the whole record context like when translating
222
+ with the "as" option.
223
+
213
224
  #### Built-in attribute translators:
214
225
 
215
226
  * [`:boolean`][bool]
@@ -3,9 +3,22 @@ module Stockboy
3
3
  # Struct-like value object for holding mapping & translation details from
4
4
  # input data fields
5
5
  #
6
- class Attribute < Struct.new(:to, :from, :translators)
6
+ class Attribute < Struct.new(:to, :from, :translators, :ignore_condition)
7
+ def ignore?(context)
8
+ if Symbol === ignore_condition
9
+ context.public_send(to).public_send(ignore_condition)
10
+ elsif ignore_condition.respond_to?(:call)
11
+ ignore_condition.call(context)
12
+ else
13
+ false
14
+ end
15
+ end
16
+
7
17
  def inspect
8
- "#<Stockboy::Attribute to=#{to.inspect}, from=#{from.inspect}, translators=#{translators}>"
18
+ "#<Stockboy::Attribute to=#{to.inspect}, from=#{from.inspect}%s%s>" % [
19
+ (", translators=#{translators}" if translators),
20
+ (", ignore=#{ignore_condition.inspect}" if ignore_condition)
21
+ ]
9
22
  end
10
23
  end
11
24
  end
@@ -52,8 +52,9 @@ module Stockboy
52
52
  from = opts.fetch(:from, key)
53
53
  from = from.to_s.freeze if from.is_a? Symbol
54
54
  translators = Array(opts[:as]).map { |t| Translations.translator_for(to, t) }
55
+ ignore_condition = opts[:ignore]
55
56
  define_singleton_method(key) { @map[key] }
56
- @map[key] = Attribute.new(to, from, translators)
57
+ @map[key] = Attribute.new(to, from, translators, ignore_condition)
57
58
  end
58
59
 
59
60
  # Fetch the attribute corresponding to the source field name
@@ -19,6 +19,7 @@ module Stockboy
19
19
  @map = map
20
20
  @table = reuse_frozen_hash_keys(attrs, map)
21
21
  @tr_table = Hash.new
22
+ @ignored_fields = []
22
23
  freeze
23
24
  end
24
25
 
@@ -27,11 +28,24 @@ module Stockboy
27
28
  # @return [Hash]
28
29
  #
29
30
  def to_hash
31
+ bulk_hash.tap do |out|
32
+ tmp_context = SourceRecord.new(out, @table)
33
+ @map.each_with_object(out) do |col|
34
+ out.delete(col.to) if ignore?(col, tmp_context)
35
+ end
36
+ end
37
+ end
38
+ alias_method :attributes, :to_hash
39
+
40
+ # Mapped output hash including ignored values
41
+ #
42
+ # @return [Hash]
43
+ #
44
+ def bulk_hash
30
45
  Hash.new.tap do |out|
31
46
  @map.each { |col| out[col.to] = translate(col) }
32
47
  end
33
48
  end
34
- alias_method :attributes, :to_hash
35
49
 
36
50
  # Return the original values mapped to attribute keys
37
51
  #
@@ -80,7 +94,7 @@ module Stockboy
80
94
  # input.email # => "ME@EXAMPLE.COM "
81
95
  #
82
96
  def input
83
- SourceRecord.new(self.raw_hash, @table)
97
+ SourceRecord.new(raw_hash, @table)
84
98
  end
85
99
 
86
100
  # Data structure representing the record's mapped & translated output values
@@ -91,7 +105,7 @@ module Stockboy
91
105
  # output.email # => "me@example.com"
92
106
  #
93
107
  def output
94
- MappedRecord.new(self.to_hash)
108
+ MappedRecord.new(bulk_hash)
95
109
  end
96
110
 
97
111
  private
@@ -102,14 +116,24 @@ module Stockboy
102
116
  fields = raw_hash
103
117
  tr_input = col.translators.reduce(input) do |value, tr|
104
118
  begin
105
- fields[col.to] = tr[value]
119
+ fields[key] = tr[value]
106
120
  SourceRecord.new(fields, @table)
107
121
  rescue
108
- fields[col.to] = nil
122
+ fields[key] = nil
109
123
  break SourceRecord.new(fields, @table)
110
124
  end
111
125
  end
112
- @tr_table[col.to] = tr_input.public_send(col.to)
126
+ @tr_table[key] = tr_input.public_send(key)
127
+ end
128
+ end
129
+
130
+ def ignore?(col, context)
131
+ return true if @ignored_fields.include? col.to
132
+ if col.ignore?(context)
133
+ @ignored_fields << col.to
134
+ true
135
+ else
136
+ false
113
137
  end
114
138
  end
115
139
 
data/lib/stockboy/job.rb CHANGED
@@ -196,12 +196,12 @@ module Stockboy
196
196
  # @return [String]
197
197
  #
198
198
  def inspect
199
- prov = "provider=#{(Stockboy::Providers.all.key(provider.class) || provider.class)}"
200
- read = "reader=#{(Stockboy::Readers.all.key(reader.class) || reader.class)}"
199
+ prov = "provider=#{Providers.all.key(provider.class) || provider.class}"
200
+ read = "reader=#{Readers.all.key(reader.class) || reader.class}"
201
201
  attr = "attributes=#{attributes.map(&:to)}"
202
202
  filt = "filters=#{filters.keys}"
203
203
  cnts = "record_counts=#{record_counts}"
204
- "#<#{self.class}:#{self.object_id} #{[prov, read, attr, filt, cnts].join(', ')}>"
204
+ "#<#{self.class}:#{self.object_id} #{prov}, #{read}, #{attr}, #{filt}, #{cnts}>"
205
205
  end
206
206
 
207
207
  private
@@ -94,11 +94,27 @@ module Stockboy::Readers
94
94
 
95
95
  private
96
96
 
97
+ # Clean incoming data based on set encoding or best information
98
+ #
99
+ # 1. Assign the given input encoding setting if available
100
+ # 2. Scrub invalid characters for the encoding. (Scrubbing does not apply
101
+ # for BINARY input, which is undefined.)
102
+ # 3. Encode to UTF-8 with considerations for undefined input. The main
103
+ # issue are control characters that are absent in UTF-8 (and ISO-8859-1)
104
+ # but are common printable characters in Windows-1252, so we preserve
105
+ # this range as a best guess.
106
+ # 4. Delete null bytes that are inserted as terminators by some "CSV" output
107
+ # 5. Delete leading/trailing garbage lines based on settings
108
+ #
97
109
  def sanitize(data)
98
- data.force_encoding(encoding) if encoding
99
- data = data.encode(universal_newline: true)
100
- .delete(0.chr)
101
- .chomp
110
+ data = data.dup
111
+ data.force_encoding encoding if encoding
112
+ data.scrub!
113
+ data.encode! Encoding::UTF_8, universal_newline: true, fallback: proc { |c|
114
+ c.force_encoding(Encoding::Windows_1252) if (127..159).cover? c.ord
115
+ }
116
+ data.delete! 0.chr
117
+ data.chomp!
102
118
  from = row_start_index(data, skip_header_rows)
103
119
  to = row_end_index(data, skip_footer_rows)
104
120
  data[from..to]
@@ -46,12 +46,42 @@ module Stockboy::Translations
46
46
  ::Date.parse(value)
47
47
  end
48
48
 
49
- def separator(value)
50
- value.include?(?/) ? ?/ : ?-
51
- end
49
+ module PatternMatching
50
+ private
51
+
52
+ module ClassMethods
53
+ def match(*patterns)
54
+ @patterns = patterns
55
+ end
56
+ attr_reader :patterns
57
+ end
58
+
59
+ def self.included(base)
60
+ base.extend ClassMethods
61
+ end
62
+
63
+ # Match date strings with 4-digit year
64
+ MATCH_YYYY = %r"\A\d{1,2}(/|-)\d{1,2}\1\d{4}\z"
65
+ SLASH = '/'.freeze
66
+
67
+ # @return [Date]
68
+ #
69
+ def parse_date(value)
70
+ ::Date.strptime(value, date_format(value))
71
+ end
72
+
73
+ def date_format(value)
74
+ self.class.patterns[yy_index(value) + sep_index(value)].freeze
75
+ end
52
76
 
53
- # Match date strings with 2-digit year
54
- MATCH_YYYY = %r"\A\d{1,2}(/|-)\d{1,2}\1\d{4}\z"
77
+ def yy_index(value)
78
+ value =~ MATCH_YYYY ? 0 : 2
79
+ end
80
+
81
+ def sep_index(value)
82
+ value.include?(SLASH) ? 1 : 0
83
+ end
84
+ end
55
85
 
56
86
  end
57
87
  end
@@ -1,5 +1,5 @@
1
1
  require 'stockboy/translator'
2
- require 'stockboy/translations/us_date'
2
+ require 'stockboy/translations/date'
3
3
 
4
4
  module Stockboy::Translations
5
5
 
@@ -26,14 +26,10 @@ module Stockboy::Translations
26
26
  # record.check_in = "1/2/12"
27
27
  # date.translate(record, :check_in) # => #<Date 2012-02-01>
28
28
  #
29
- class UKDate < Stockboy::Translations::USDate
29
+ class UKDate < Stockboy::Translations::Date
30
+ include Stockboy::Translations::Date::PatternMatching
30
31
 
31
- private
32
-
33
- def date_format(value)
34
- x = separator(value)
35
- value =~ MATCH_YYYY ? "%d#{x}%m#{x}%Y" : "%d#{x}%m#{x}%y"
36
- end
32
+ match '%d-%m-%Y', '%d/%m/%Y', '%d-%m-%y', '%d/%m/%y'
37
33
 
38
34
  end
39
35
  end
@@ -27,19 +27,9 @@ module Stockboy::Translations
27
27
  # date.translate(record, :check_in) # => #<Date 2012-02-01>
28
28
  #
29
29
  class USDate < Stockboy::Translations::Date
30
+ include Stockboy::Translations::Date::PatternMatching
30
31
 
31
- private
32
-
33
- # @return [Date]
34
- #
35
- def parse_date(value)
36
- ::Date.strptime(value, date_format(value))
37
- end
38
-
39
- def date_format(value)
40
- x = separator(value)
41
- value =~ MATCH_YYYY ? "%m#{x}%d#{x}%Y" : "%m#{x}%d#{x}%y"
42
- end
32
+ match '%m-%d-%Y', '%m/%d/%Y', '%m-%d-%y', '%m/%d/%y'
43
33
 
44
34
  end
45
35
  end
@@ -1,3 +1,3 @@
1
1
  module Stockboy
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/spec/spec_helper.rb CHANGED
@@ -17,7 +17,7 @@ if ENV['DEBUG']
17
17
  require 'pry'
18
18
  end
19
19
 
20
-
20
+ require 'rspec/its'
21
21
  require 'ostruct'
22
22
  require 'savon'
23
23
  require 'savon/mock/spec_helper'
@@ -7,6 +7,7 @@ module Stockboy
7
7
  subject do
8
8
  AttributeMap.new do
9
9
  email
10
+ score ignore: :zero?
10
11
  updated_at from: 'statusDate', as: [proc{ |v| Date.parse(v) }]
11
12
  end
12
13
  end
@@ -43,6 +44,10 @@ module Stockboy
43
44
  subject[:updated_at].translators.first.call("2012-01-01").should == Date.new(2012,1,1)
44
45
  end
45
46
 
47
+ it "sets ignore conditions" do
48
+ expect( subject[:score].ignore_condition ).to be :zero?
49
+ end
50
+
46
51
  it "has attr accessors" do
47
52
  subject.email.should be_a Attribute
48
53
  end
@@ -52,8 +57,8 @@ module Stockboy
52
57
  end
53
58
 
54
59
  it "is enumerable" do
55
- subject.map(&:from).should == ["email", "statusDate"]
56
- subject.map(&:to).should == [:email, :updated_at]
60
+ subject.map(&:from).should == ["email", "score", "statusDate"]
61
+ subject.map(&:to).should == [:email, :score, :updated_at]
57
62
  end
58
63
 
59
64
  describe "#insert" do
@@ -4,8 +4,28 @@ require 'stockboy/attribute'
4
4
  module Stockboy
5
5
  describe Attribute do
6
6
  it "describes its attrs" do
7
- attr = Attribute.new :outfield, "infield", [:one, :two]
8
- attr.inspect.should == %{#<Stockboy::Attribute to=:outfield, from="infield", translators=[:one, :two]>}
7
+ attr = Attribute.new :outfield, "infield", [:one, :two], :nil?
8
+ attr.inspect.should == %{#<Stockboy::Attribute to=:outfield, from="infield", translators=[:one, :two], ignore=:nil?>}
9
+ end
10
+
11
+ describe :ignore? do
12
+ it "is false by default" do
13
+ attr = Attribute.new :email, "email", []
14
+ attr.ignore?(double email: "").should be false
15
+ end
16
+
17
+ it "extracts symbols from a record" do
18
+ attr = Attribute.new :email, "email", [], :blank?
19
+ attr.ignore?(double email: "").should be true
20
+ attr.ignore?(double email: "@").should be false
21
+ end
22
+
23
+ it "yields records to a proc" do
24
+ attr = Attribute.new :email, "email", [], ->(r) { not r.email.include? "@" }
25
+ attr.ignore?(double email: "").should be true
26
+ attr.ignore?(double email: "@").should be false
27
+ end
28
+
9
29
  end
10
30
  end
11
31
  end
@@ -33,6 +33,13 @@ module Stockboy
33
33
  subject = CandidateRecord.new(hash_attrs, map).to_hash
34
34
  subject[:full_name].class.should == String
35
35
  end
36
+
37
+ it "omits ignored attributes" do
38
+ invalid_email = ->(r) { r.email.include?('example.com') }
39
+ map = AttributeMap.new { id ; email ignore: invalid_email }
40
+ subject = CandidateRecord.new(hash_attrs, map).to_hash
41
+ subject.should == { :id => '1' }
42
+ end
36
43
  end
37
44
 
38
45
  describe "#raw_hash" do
@@ -82,7 +89,7 @@ module Stockboy
82
89
  describe "#to_model" do
83
90
  it "should instantiate a new model" do
84
91
  model = Class.new(OpenStruct)
85
- model.should_receive(:new).with({id: '1', name: 'Arthur Dent'})
92
+ expect(model).to receive(:new).with({id: '1', name: 'Arthur Dent'})
86
93
  map = AttributeMap.new { id; name from: 'full_name' }
87
94
  subject = CandidateRecord.new(hash_attrs, map)
88
95
 
@@ -113,6 +120,12 @@ module Stockboy
113
120
  subject = CandidateRecord.new(hash_attrs, map)
114
121
  subject.output.name.should == 'Arthur Dent'
115
122
  end
123
+
124
+ it "returns translated values for ignored keys" do
125
+ map = AttributeMap.new { email ignore: ->(r){ r.email.include?("example.com") } }
126
+ subject = CandidateRecord.new(hash_attrs, map)
127
+ subject.output.email.should == "adent@example.com"
128
+ end
116
129
  end
117
130
  end
118
131
 
@@ -137,12 +150,12 @@ module Stockboy
137
150
 
138
151
  it "returns nil when raw field is unmatched" do
139
152
  key = subject.partition({beta: proc{ |raw| raw.name =~ /B/ }})
140
- key.should be_nil
153
+ key.should be nil
141
154
  end
142
155
 
143
156
  it "returns nil when translated field is unmatched" do
144
157
  key = subject.partition({beta: proc{ |raw,out| out.name =~ /B/ }})
145
- key.should be_nil
158
+ key.should be nil
146
159
  end
147
160
  end
148
161
 
@@ -37,7 +37,7 @@ module Stockboy
37
37
  end
38
38
 
39
39
  it "initializes arguments" do
40
- provider_class.should_receive(:new).with(password:'foo')
40
+ expect(provider_class).to receive(:new).with(password:'foo')
41
41
  subject.provider :ftp, password: 'foo'
42
42
  end
43
43
  end
@@ -68,7 +68,7 @@ module Stockboy
68
68
 
69
69
  it "initializes arguments" do
70
70
  reader_stub = double(:reader)
71
- reader_class.should_receive(:new).with(col_sep: '|').and_return(reader_stub)
71
+ expect(reader_class).to receive(:new).with(col_sep: '|').and_return(reader_stub)
72
72
  subject.reader reader_class, col_sep: '|'
73
73
  subject.config[:reader].should == reader_stub
74
74
  end
@@ -77,7 +77,7 @@ module Stockboy
77
77
  describe "#attributes" do
78
78
  it "initializes a block" do
79
79
  attribute_map = double
80
- AttributeMap.should_receive(:new).and_return(attribute_map)
80
+ expect(AttributeMap).to receive(:new).and_return(attribute_map)
81
81
  subject.attributes &proc{}
82
82
  subject.config[:attributes].should be attribute_map
83
83
  end
@@ -85,7 +85,7 @@ module Stockboy
85
85
  it "replaces existing attributes" do
86
86
  subject.attribute :first_name
87
87
  subject.attributes do last_name end
88
- subject.config[:attributes][:first_name].should be_nil
88
+ subject.config[:attributes][:first_name].should be nil
89
89
  subject.config[:attributes][:last_name].should be_an Attribute
90
90
  end
91
91
  end
@@ -17,7 +17,7 @@ module Stockboy
17
17
  let(:chain) { FilterChain.new(no_angels: filter1, no_daleks: filter2) }
18
18
 
19
19
  it "calls reset on all members" do
20
- filter2.should_receive(:reset)
20
+ expect(filter2).to receive(:reset)
21
21
  chain.reset
22
22
  end
23
23
 
@@ -21,21 +21,21 @@ module Stockboy
21
21
 
22
22
  context "matching raw value" do
23
23
  it "returns true for match" do
24
- filter.call(double(species:"babylichtus"), empty_values).should be_true
24
+ filter.call(double(species:"babylichtus"), empty_values).should be true
25
25
  end
26
26
 
27
27
  it "returns false for no match" do
28
- filter.call(double(species:"triceratops"), empty_values).should be_false
28
+ filter.call(double(species:"triceratops"), empty_values).should be false
29
29
  end
30
30
  end
31
31
 
32
32
  context "matching translated value" do
33
33
  it "returns true for match" do
34
- filter.call(empty_values, double(species:"babelfish")).should be_true
34
+ filter.call(empty_values, double(species:"babelfish")).should be true
35
35
  end
36
36
 
37
37
  it "returns false for no match" do
38
- filter.call(empty_values, double(species:"rhinoceros")).should be_false
38
+ filter.call(empty_values, double(species:"rhinoceros")).should be false
39
39
  end
40
40
  end
41
41
  end
@@ -5,22 +5,22 @@ describe Stockboy::Filters::MissingEmail do
5
5
  subject(:filter) { described_class.new(:e) }
6
6
  it 'allows email addresses' do
7
7
  record = OpenStruct.new(e: 'me@example.com')
8
- filter.call(record, record).should be_false
8
+ filter.call(record, record).should be false
9
9
  end
10
10
 
11
11
  it 'catches empty strings' do
12
12
  record = OpenStruct.new(e: '')
13
- filter.call(record, record).should be_true
13
+ filter.call(record, record).should be true
14
14
  end
15
15
 
16
16
  it 'catches hyphen placeholders' do
17
17
  record = OpenStruct.new(e: '-')
18
- filter.call(record, record).should be_true
18
+ filter.call(record, record).should be true
19
19
  end
20
20
 
21
21
  it 'uses translated output value' do
22
22
  input = OpenStruct.new(e: '', other: 'me@example.com')
23
23
  output = OpenStruct.new(e: input.other)
24
- filter.call(input, output).should be_false
24
+ filter.call(input, output).should be false
25
25
  end
26
26
  end
@@ -85,22 +85,22 @@ module Stockboy
85
85
  end
86
86
 
87
87
  it "should read a file from a path" do
88
- File.should_receive(:read).with("#{jobs_path}/test_job.rb")
88
+ expect(File).to receive(:read).with("#{jobs_path}/test_job.rb")
89
89
  Job.define("test_job")
90
90
  end
91
91
 
92
92
  it "assigns a registered provider from a symbol" do
93
- Stockboy::Providers.should_receive(:find)
94
- .with(:ftp)
95
- .and_return(TestProvider)
93
+ expect(Stockboy::Providers).to receive(:find)
94
+ .with(:ftp)
95
+ .and_return(TestProvider)
96
96
  job = Job.define("test_job")
97
97
  job.provider.should be_a TestProvider
98
98
  end
99
99
 
100
100
  it "assigns a registered reader from a symbol" do
101
- Stockboy::Readers.should_receive(:find)
102
- .with(:csv)
103
- .and_return(TestReader)
101
+ expect(Stockboy::Readers).to receive(:find)
102
+ .with(:csv)
103
+ .and_return(TestReader)
104
104
  job = Job.define("test_job")
105
105
  job.reader.should be_a TestReader
106
106
  end
@@ -247,9 +247,9 @@ module Stockboy
247
247
  end
248
248
 
249
249
  it "indicates if the job has been processed" do
250
- job.processed?.should be_false
250
+ job.processed?.should be false
251
251
  job.process
252
- job.processed?.should be_true
252
+ job.processed?.should be true
253
253
  end
254
254
  end
255
255
 
@@ -76,7 +76,7 @@ module Stockboy
76
76
 
77
77
  describe "#data_size" do
78
78
  subject(:repeater) { ProviderRepeater.new(provider) }
79
- its(:data_size) { should be_nil }
79
+ its(:data_size) { should be nil }
80
80
 
81
81
  context "after iterating" do
82
82
  before { repeater.data do |data| end }
@@ -86,7 +86,7 @@ module Stockboy
86
86
 
87
87
  describe "#data_time" do
88
88
  subject(:repeater) { ProviderRepeater.new(provider) }
89
- its(:data_time) { should be_nil }
89
+ its(:data_time) { should be nil }
90
90
 
91
91
  context "after iterating" do
92
92
  before { repeater.data do |data| end }
@@ -96,7 +96,7 @@ module Stockboy
96
96
 
97
97
  describe "#data?" do
98
98
  subject(:repeater) { ProviderRepeater.new(provider) }
99
- its(:data?) { should be_nil }
99
+ its(:data?) { should be nil }
100
100
 
101
101
  context "after iterating" do
102
102
  before { repeater.data do |data| end }
@@ -76,7 +76,7 @@ module Stockboy
76
76
 
77
77
  describe "#data_size" do
78
78
  subject(:provider) { ProviderSubclass.new }
79
- its(:data_size) { should be_nil }
79
+ its(:data_size) { should be nil }
80
80
 
81
81
  context "after fetching" do
82
82
  before { provider.fetch_data }
@@ -86,7 +86,7 @@ module Stockboy
86
86
 
87
87
  describe "#data_time" do
88
88
  subject(:provider) { ProviderSubclass.new }
89
- its(:data_time) { should be_nil }
89
+ its(:data_time) { should be nil }
90
90
 
91
91
  context "after fetching" do
92
92
  before { provider.fetch_data }
@@ -96,7 +96,7 @@ module Stockboy
96
96
 
97
97
  describe "#data?" do
98
98
  subject(:provider) { ProviderSubclass.new }
99
- its(:data?) { should be_nil }
99
+ its(:data?) { should be nil }
100
100
 
101
101
  context "after fetching" do
102
102
  before { provider.fetch_data }
@@ -52,7 +52,7 @@ module Stockboy
52
52
 
53
53
  context "with an unmatched string" do
54
54
  before { provider.file_name = "missing" }
55
- it { should be_nil }
55
+ it { should be nil }
56
56
  end
57
57
  end
58
58
 
@@ -61,8 +61,8 @@ module Stockboy
61
61
 
62
62
  it "fails with an error if the file doesn't exist" do
63
63
  provider.file_name = "missing-file.csv"
64
- provider.data.should be_nil
65
- provider.valid?.should be_false
64
+ provider.data.should be nil
65
+ provider.valid?.should be false
66
66
  provider.errors.first.should match /not found/
67
67
  end
68
68
 
@@ -90,21 +90,21 @@ module Stockboy
90
90
  it "skips old files with :since" do
91
91
  expect_any_instance_of(::File).to receive(:mtime).and_return last_week
92
92
  provider.since = recently
93
- provider.data.should be_nil
93
+ provider.data.should be nil
94
94
  provider.errors.first.should == "no new files since #{recently}"
95
95
  end
96
96
 
97
97
  it "skips large files with :file_smaller" do
98
98
  expect_any_instance_of(::File).to receive(:size).and_return 1001
99
99
  provider.file_smaller = 1000
100
- provider.data.should be_nil
100
+ provider.data.should be nil
101
101
  provider.errors.first.should == "file size larger than 1000"
102
102
  end
103
103
 
104
104
  it "skips small files with :file_larger" do
105
105
  expect_any_instance_of(::File).to receive(:size).and_return 999
106
106
  provider.file_larger = 1000
107
- provider.data.should be_nil
107
+ provider.data.should be nil
108
108
  provider.errors.first.should == "file size smaller than 1000"
109
109
  end
110
110
  end
@@ -49,8 +49,8 @@ module Stockboy
49
49
  provider.client { |f| connection = f }
50
50
 
51
51
  connection.should be_a Net::FTP
52
- connection.binary.should be_true
53
- connection.passive.should be_true
52
+ connection.binary.should be true
53
+ connection.passive.should be true
54
54
  end
55
55
 
56
56
  it "should return yielded result" do
@@ -94,7 +94,7 @@ module Stockboy
94
94
 
95
95
  provider.file_newer = Time.new(2010,1,1)
96
96
 
97
- provider.data.should be_nil
97
+ provider.data.should be nil
98
98
  end
99
99
  end
100
100
 
@@ -64,7 +64,7 @@ module Stockboy
64
64
 
65
65
  context "with no messages found" do
66
66
  before { mock_imap_search [] => [] }
67
- it { should be_nil }
67
+ it { should be nil }
68
68
  end
69
69
 
70
70
  context "with a found message" do
@@ -79,7 +79,7 @@ module Stockboy
79
79
 
80
80
  context "validating incorrect filename string" do
81
81
  before { provider.attachment = "wrong_report.csv" }
82
- it { should be_nil }
82
+ it { should be nil }
83
83
  end
84
84
 
85
85
  context "validating correct filename pattern" do
@@ -89,7 +89,7 @@ module Stockboy
89
89
 
90
90
  context "validating incorrect filename pattern" do
91
91
  before { provider.attachment = /^wrong.*\.csv$/ }
92
- it { should be_nil }
92
+ it { should be nil }
93
93
  end
94
94
 
95
95
  context "validating correct smaller size" do
@@ -99,7 +99,7 @@ module Stockboy
99
99
 
100
100
  context "validating incorrect smaller size" do
101
101
  before { provider.smaller_than = 10 }
102
- it { should be_nil }
102
+ it { should be nil }
103
103
  end
104
104
 
105
105
  context "validating correct larger size" do
@@ -109,7 +109,7 @@ module Stockboy
109
109
 
110
110
  context "validating correct larger size" do
111
111
  before { provider.larger_than = 2048 }
112
- it { should be_nil }
112
+ it { should be nil }
113
113
  end
114
114
 
115
115
  end
@@ -4,25 +4,25 @@ require 'stockboy/providers'
4
4
  module Stockboy
5
5
  describe Providers do
6
6
 
7
- let(:provider) { double("provider") }
7
+ let(:provider) { Class.new }
8
8
 
9
9
  describe ".register" do
10
10
  it "registers a key and class" do
11
- Providers.register(:snailmail, provider).should === provider
11
+ Providers.register(:snailmail, provider).should be provider
12
12
  end
13
13
  end
14
14
 
15
15
  describe ".find" do
16
16
  it "returns a provider class" do
17
17
  Providers.register(:snailmail, provider)
18
- Providers.find(:snailmail).should === provider
18
+ Providers.find(:snailmail).should be provider
19
19
  end
20
20
  end
21
21
 
22
22
  describe ".[]" do
23
23
  it "returns a provider class" do
24
24
  Providers.register(:snailmail, provider)
25
- Providers[:snailmail].should === provider
25
+ Providers[:snailmail].should be provider
26
26
  end
27
27
  end
28
28
 
@@ -7,10 +7,10 @@ module Stockboy
7
7
  subject(:reader) { Stockboy::Readers::CSV.new }
8
8
 
9
9
  describe "default options" do
10
- its(:row_sep) { should be_nil }
11
- its(:col_sep) { should be_nil }
12
- its(:quote_char) { should be_nil }
13
- its(:headers) { should be_true }
10
+ its(:row_sep) { should be nil }
11
+ its(:col_sep) { should be nil }
12
+ its(:quote_char) { should be nil }
13
+ its(:headers) { should be true }
14
14
  its(:skip_header_rows) { should == 0 }
15
15
  its(:skip_footer_rows) { should == 0 }
16
16
  end
@@ -51,6 +51,15 @@ module Stockboy
51
51
  [{"city" => "Vancouver", "state" => nil, "country" => "Canada"}]
52
52
  end
53
53
 
54
+ it "scrubs invalid encoding characters in Unicode" do
55
+ reader.headers = %w[depart arrive]
56
+ reader.encoding = 'UTF-8'
57
+ garbage = 191.chr.force_encoding('UTF-8')
58
+ data = "Z#{garbage}rich,Genève"
59
+ reader.parse(data).should ==
60
+ [{"depart" => "Z\u{FFFD}rich", "arrive" => "Genève"}]
61
+ end
62
+
54
63
  it "strips preamble header rows" do
55
64
  reader.skip_header_rows = 2
56
65
  data = "IGNORE\r\nCOMMENTS\r\nid,name\r\n42,Arthur Dent"
@@ -85,12 +85,12 @@ module Stockboy
85
85
 
86
86
  describe "#sheet", pending: "Hard to test this due to roo. Needs a test case with fixtures" do
87
87
  let(:sheets) { ['Towels', 'Lemons'] }
88
- let(:expectation) { mock(:spreadsheet, sheets: sheets).should_receive(:default_sheet=) }
89
- let(:spreadsheet) { expectation }
88
+ let(:be_selected) { receive(:default_sheet=) }
89
+ let(:spreadsheet) { double(:spreadsheet, sheets: sheets) }
90
90
  before { subject.stub!(:with_spreadsheet).and_yield(spreadsheet) }
91
91
 
92
92
  context "with :first" do
93
- let(:spreadsheet) { expectation.with('Towels') }
93
+ before { expect(spreadsheet).to be_selected.with('Towels') }
94
94
 
95
95
  it "calls on first sheet by name" do
96
96
  subject.sheet = :first
@@ -99,7 +99,7 @@ module Stockboy
99
99
  end
100
100
 
101
101
  context "with a string" do
102
- let(:spreadsheet) { expectation.with('Towels') }
102
+ before { expect(spreadsheet).to be_selected.with('Towels') }
103
103
 
104
104
  it "passes unchanged" do
105
105
  subject.sheet = 'Towels'
@@ -108,7 +108,7 @@ module Stockboy
108
108
  end
109
109
 
110
110
  context "with an integer" do
111
- let(:spreadsheet) { expectation.with('Towels') }
111
+ before { expect(spreadsheet).to be_selected.with('Towels') }
112
112
 
113
113
  it "gets sheet name" do
114
114
  subject.sheet = 1
@@ -15,8 +15,8 @@ module Stockboy
15
15
  elements: ['SomeNested', 'Record']
16
16
  )
17
17
 
18
- reader.options[:strip_namespaces].should be_true
19
- reader.options[:advanced_typecasting].should be_true
18
+ reader.options[:strip_namespaces].should be true
19
+ reader.options[:advanced_typecasting].should be true
20
20
  reader.options[:convert_tags_to].should be_a Proc
21
21
  reader.options[:parser].should == :nokogiri
22
22
  reader.elements.should == ['some_nested', 'record']
@@ -32,8 +32,8 @@ module Stockboy
32
32
  elements ['SomeNested', 'Record']
33
33
  end
34
34
 
35
- reader.options[:strip_namespaces].should be_true
36
- reader.options[:advanced_typecasting].should be_true
35
+ reader.options[:strip_namespaces].should be true
36
+ reader.options[:advanced_typecasting].should be true
37
37
  reader.options[:convert_tags_to].should be_a Proc
38
38
  reader.options[:parser].should == :nokogiri
39
39
  reader.elements.should == ['some_nested', 'record']
@@ -8,21 +8,21 @@ module Stockboy
8
8
 
9
9
  describe ".register" do
10
10
  it "registers a key for a reader class" do
11
- Readers.register(:markup, reader_class).should == reader_class
11
+ Readers.register(:markup, reader_class).should be reader_class
12
12
  end
13
13
  end
14
14
 
15
15
  describe ".find" do
16
16
  it "returns a reader class" do
17
17
  Readers.register(:markup, reader_class)
18
- Readers.find(:markup).should == reader_class
18
+ Readers.find(:markup).should be reader_class
19
19
  end
20
20
  end
21
21
 
22
22
  describe ".[]" do
23
23
  it "returns a reader class" do
24
24
  Readers.register(:markup, reader_class)
25
- Readers[:markup].should == reader_class
25
+ Readers[:markup].should be reader_class
26
26
  end
27
27
  end
28
28
 
@@ -18,7 +18,7 @@ module Stockboy
18
18
 
19
19
  describe ".template_path" do
20
20
  it "returns nil when missing" do
21
- TemplateFile.find("not_here").should be_nil
21
+ TemplateFile.find("not_here").should be nil
22
22
  end
23
23
 
24
24
  it "returns a file path when found" do
@@ -10,7 +10,7 @@ module Stockboy
10
10
  describe "#call" do
11
11
  it "returns nil for an empty string" do
12
12
  result = subject.call start: ""
13
- result.should be_nil
13
+ result.should be nil
14
14
  end
15
15
 
16
16
  it "returns a date unmodified" do
@@ -9,7 +9,7 @@ module Stockboy
9
9
  describe "#call" do
10
10
  it "returns nil for an empty string" do
11
11
  result = subject.call total: ""
12
- result.should be_nil
12
+ result.should be nil
13
13
  end
14
14
 
15
15
  it "returns a decimal" do
@@ -9,7 +9,7 @@ module Stockboy
9
9
  describe "#call" do
10
10
  it "returns nil for an empty string" do
11
11
  result = subject.call id: ""
12
- result.should be_nil
12
+ result.should be nil
13
13
  end
14
14
 
15
15
  it "returns an integer" do
@@ -9,7 +9,7 @@ module Stockboy
9
9
  describe "#call" do
10
10
  it "returns nil for an empty string" do
11
11
  result = subject.call start: ""
12
- result.should be_nil
12
+ result.should be nil
13
13
  end
14
14
 
15
15
  it "returns a time" do
@@ -9,7 +9,7 @@ module Stockboy
9
9
  describe "#call" do
10
10
  it "returns nil for an empty string" do
11
11
  result = subject.call start: ""
12
- result.should be_nil
12
+ result.should be nil
13
13
  end
14
14
 
15
15
  it "translates DD/MM/YYYY" do
@@ -9,7 +9,7 @@ module Stockboy
9
9
  describe "#call" do
10
10
  it "returns nil for an empty string" do
11
11
  result = subject.call start: ""
12
- result.should be_nil
12
+ result.should be nil
13
13
  end
14
14
 
15
15
  it "translates MM/DD/YYYY" do
@@ -68,11 +68,11 @@ module Stockboy
68
68
 
69
69
  describe ".find" do
70
70
  it "returns a callable translator" do
71
- callable = ->(i){ i.upcase }
71
+ callable = ->(i){ i.message.upcase }
72
72
  Translations.register :shout, callable
73
73
 
74
- Translations.find(:shout).should == callable
75
- Translations[:shout].should == callable
74
+ Translations.find(:shout).should be callable
75
+ Translations[:shout].should be callable
76
76
  end
77
77
  end
78
78
 
data/stockboy.gemspec CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
 
24
24
  s.add_development_dependency "rake"
25
25
  s.add_development_dependency "rspec"
26
+ s.add_development_dependency "rspec-its"
26
27
  # s.add_development_dependency "vcr"
27
28
 
28
29
  s.add_runtime_dependency "roo", ">= 1.12.2"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stockboy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Vit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-19 00:00:00.000000000 Z
11
+ date: 2015-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-its
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: roo
43
57
  requirement: !ruby/object:Gem::Requirement