stockboy 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +11 -0
- data/lib/stockboy/attribute.rb +15 -2
- data/lib/stockboy/attribute_map.rb +2 -1
- data/lib/stockboy/candidate_record.rb +30 -6
- data/lib/stockboy/job.rb +3 -3
- data/lib/stockboy/readers/csv.rb +20 -4
- data/lib/stockboy/translations/date.rb +35 -5
- data/lib/stockboy/translations/uk_date.rb +4 -8
- data/lib/stockboy/translations/us_date.rb +2 -12
- data/lib/stockboy/version.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/stockboy/attribute_map_spec.rb +7 -2
- data/spec/stockboy/attribute_spec.rb +22 -2
- data/spec/stockboy/candidate_record_spec.rb +16 -3
- data/spec/stockboy/configurator_spec.rb +4 -4
- data/spec/stockboy/filter_chain_spec.rb +1 -1
- data/spec/stockboy/filter_spec.rb +4 -4
- data/spec/stockboy/filters/missing_email_spec.rb +4 -4
- data/spec/stockboy/job_spec.rb +9 -9
- data/spec/stockboy/provider_repeater_spec.rb +3 -3
- data/spec/stockboy/provider_spec.rb +3 -3
- data/spec/stockboy/providers/file_spec.rb +6 -6
- data/spec/stockboy/providers/ftp_spec.rb +3 -3
- data/spec/stockboy/providers/imap_spec.rb +5 -5
- data/spec/stockboy/providers_spec.rb +4 -4
- data/spec/stockboy/readers/csv_spec.rb +13 -4
- data/spec/stockboy/readers/spreadsheet_spec.rb +5 -5
- data/spec/stockboy/readers/xml_spec.rb +4 -4
- data/spec/stockboy/readers_spec.rb +3 -3
- data/spec/stockboy/template_file_spec.rb +1 -1
- data/spec/stockboy/translations/date_spec.rb +1 -1
- data/spec/stockboy/translations/decimal_spec.rb +1 -1
- data/spec/stockboy/translations/integer_spec.rb +1 -1
- data/spec/stockboy/translations/time_spec.rb +1 -1
- data/spec/stockboy/translations/uk_date_spec.rb +1 -1
- data/spec/stockboy/translations/us_date_spec.rb +1 -1
- data/spec/stockboy/translations_spec.rb +3 -3
- data/stockboy.gemspec +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f39685f84983ede11ee77ffe5ab39982e37ddac7
|
4
|
+
data.tar.gz: c16642a9c384a71c72def32658d8e4764ab46b14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d7f4377f7973b9e88d91ea807c12987385e594ca9f55b1594dbdc652ad8aea3cf674e431394ed840d60b11454404975cf4d9dc77b3a8a3409a760b1668316ef
|
7
|
+
data.tar.gz: 7dd65ee93fbf9a6df91f052b5505fe13d2b4d1ae2901a612a7ccd87589f1f5662cf8154f5d7d397a4d59779c23cd4ddf4abbee5c70f78a0204130c9cd329e9f5
|
data/CHANGELOG.md
CHANGED
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]
|
data/lib/stockboy/attribute.rb
CHANGED
@@ -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}
|
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(
|
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(
|
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[
|
119
|
+
fields[key] = tr[value]
|
106
120
|
SourceRecord.new(fields, @table)
|
107
121
|
rescue
|
108
|
-
fields[
|
122
|
+
fields[key] = nil
|
109
123
|
break SourceRecord.new(fields, @table)
|
110
124
|
end
|
111
125
|
end
|
112
|
-
@tr_table[
|
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=#{
|
200
|
-
read = "reader=#{
|
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} #{
|
204
|
+
"#<#{self.class}:#{self.object_id} #{prov}, #{read}, #{attr}, #{filt}, #{cnts}>"
|
205
205
|
end
|
206
206
|
|
207
207
|
private
|
data/lib/stockboy/readers/csv.rb
CHANGED
@@ -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
|
99
|
-
data
|
100
|
-
|
101
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
54
|
-
|
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/
|
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::
|
29
|
+
class UKDate < Stockboy::Translations::Date
|
30
|
+
include Stockboy::Translations::Date::PatternMatching
|
30
31
|
|
31
|
-
|
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
|
-
|
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
|
data/lib/stockboy/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -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.
|
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
|
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
|
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.
|
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.
|
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.
|
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
|
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
|
@@ -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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
24
|
+
filter.call(input, output).should be false
|
25
25
|
end
|
26
26
|
end
|
data/spec/stockboy/job_spec.rb
CHANGED
@@ -85,22 +85,22 @@ module Stockboy
|
|
85
85
|
end
|
86
86
|
|
87
87
|
it "should read a file from a path" do
|
88
|
-
File.
|
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.
|
94
|
-
|
95
|
-
|
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.
|
102
|
-
|
103
|
-
|
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
|
250
|
+
job.processed?.should be false
|
251
251
|
job.process
|
252
|
-
job.processed?.should
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
65
|
-
provider.valid?.should
|
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
|
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
|
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
|
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
|
53
|
-
connection.passive.should
|
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
|
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
|
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
|
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
|
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
|
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
|
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) {
|
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
|
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
|
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
|
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
|
11
|
-
its(:col_sep) { should
|
12
|
-
its(:quote_char) { should
|
13
|
-
its(:headers) { should
|
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(:
|
89
|
-
let(:spreadsheet) {
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
19
|
-
reader.options[:advanced_typecasting].should
|
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
|
36
|
-
reader.options[:advanced_typecasting].should
|
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
|
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
|
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
|
25
|
+
Readers[:markup].should be reader_class
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -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
|
75
|
-
Translations[:shout].should
|
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
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.
|
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:
|
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
|