sports_south 3.0.2 → 5.0.1

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: 7d132005031036050cb4e55d76aa4455d519fd69
4
- data.tar.gz: 946d58824e49f0e1cce45a0dacc8353130bc91b4
3
+ metadata.gz: f72a7e530a665693c47a5b7da6ae000d965c44bd
4
+ data.tar.gz: d2f7c4c346b909c0bff5c9b0d5e5a6e6ee017068
5
5
  SHA512:
6
- metadata.gz: b009cb4927ed59d0ca25afd0e5a74b7ae475f4438161b7d7ade10aec49a6fa583690cb80a4cda0031f60d29c6c16ab90c6b980bd828ce6dd9ccd31f214e1db54
7
- data.tar.gz: e7878d0042ed306f8fdc67b10cbcb8c2b6d1c9dc47f6eeb5473d9534aceca9a55e3d2bb0902b241bf1dfe28bfb35c11e9de05303975dfd400baab0940061b8cc
6
+ metadata.gz: 0427e7620531e3cba9c3301e78ad73bcabaf07411ed6ad56c9070d76f566b511f301e02d9e1bfab63b331d7d2298bf20b3c5691751cd39ef6ca1b3a6a6e4bb10
7
+ data.tar.gz: be0a98dd98e6459103c059e14fb66ba7d93fb526c64ee21d45b0e358450cebde233ae3cf003f84b56673a159c6a5372dae831c0df223379ca0e391711404b763
@@ -0,0 +1,41 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ # specify the version you desire here
10
+ - image: circleci/ruby:2.4.1-node-browsers
11
+
12
+ working_directory: ~/repo
13
+
14
+ steps:
15
+ - checkout
16
+
17
+ # Download and cache dependencies
18
+ - restore_cache:
19
+ keys:
20
+ - v1-dependencies-{{ checksum "sports_south.gemspec" }}
21
+ # fallback to using the latest cache if no exact match is found
22
+ - v1-dependencies-
23
+
24
+ - run:
25
+ name: install dependencies
26
+ command: |
27
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
28
+
29
+ - save_cache:
30
+ paths:
31
+ - ./vendor/bundle
32
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}
33
+
34
+ # run tests!
35
+ - run:
36
+ name: run tests
37
+ command: |
38
+ mkdir /tmp/test-results
39
+ TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
40
+
41
+ bundle exec rspec --format documentation $TEST_FILES
@@ -1 +1 @@
1
- 2.3.1
1
+ 2.3.7
@@ -1,8 +1,10 @@
1
1
  require 'sports_south/version'
2
2
 
3
+ require 'cgi'
3
4
  require 'json'
4
5
  require 'net/http'
5
6
  require 'nokogiri'
7
+ require 'tempfile'
6
8
 
7
9
  require 'sports_south/base'
8
10
  require 'sports_south/brand'
@@ -1,5 +1,3 @@
1
- require 'cgi'
2
-
3
1
  module SportsSouth
4
2
  # Holds methods common to all classes.
5
3
  class Base
@@ -50,10 +48,12 @@ module SportsSouth
50
48
  # http, request = get_http_and_request(<api_url>, <endpoint>)
51
49
  def self.get_http_and_request(api_url, endpoint)
52
50
  uri = URI([api_url, endpoint].join)
51
+
53
52
  http = Net::HTTP.new(uri.host, uri.port)
54
53
  http.read_timeout = TIMEOUT
54
+
55
55
  request = Net::HTTP::Post.new(uri.request_uri)
56
- request['User-Agent'] = USER_AGENT
56
+ request['User-Agent'] = USER_AGENT
57
57
  request['Content-Type'] = CONTENT_TYPE
58
58
 
59
59
  return http, request
@@ -72,5 +72,34 @@ module SportsSouth
72
72
  end
73
73
  def sanitize_response(*args); self.class.sanitize_response(*args); end
74
74
 
75
+ def download_to_tempfile(http, request)
76
+ preformatted_tempfile = Tempfile.new(['preformatted-', '.txt'])
77
+
78
+ # Stream the response to disk.
79
+ # This file is not yet valid XML (the angle brackets are escaped).
80
+ http.request(request) do |response|
81
+ File.open(preformatted_tempfile, 'w') do |file|
82
+ response.read_body do |chunk|
83
+ file.write(chunk.force_encoding('UTF-8'))
84
+ end
85
+ end
86
+ end
87
+ preformatted_tempfile.close
88
+
89
+ # Now we need to read the file line-by-line and unescape the angle brackets.
90
+ # The new (properly formatted) XML will be in a secondary tempfile.
91
+ converted_tempfile = Tempfile.new(['formated-', '.xml'])
92
+
93
+ File.open(preformatted_tempfile, 'r') do |file|
94
+ file.each_line do |line|
95
+ converted_tempfile.puts CGI.unescapeHTML(line)
96
+ end
97
+ end
98
+
99
+ # Return the (still opened) tempfile
100
+ # Since it's sill open you may need to '#rewind' it first before usage
101
+ return converted_tempfile
102
+ end
103
+
75
104
  end
76
105
  end
@@ -2,6 +2,7 @@ module SportsSouth
2
2
  class Catalog < Base
3
3
 
4
4
  API_URL = 'http://webservices.theshootingwarehouse.com/smart/inventory.asmx'
5
+ ITEM_NODE_NAME = 'Table'
5
6
 
6
7
  CATALOG_CODES = {
7
8
  'S' => :special,
@@ -35,7 +36,7 @@ module SportsSouth
35
36
  @brands = SportsSouth::Brand.all(options)
36
37
  end
37
38
 
38
- def self.all(options = {}, &block)
39
+ def self.all(options = {})
39
40
  requires!(options, :username, :password)
40
41
 
41
42
  if options[:last_updated]
@@ -46,16 +47,10 @@ module SportsSouth
46
47
 
47
48
  options[:last_item] ||= '-1'
48
49
 
49
- new(options).all &block
50
+ new(options).all
50
51
  end
51
52
 
52
- def self.get_description(item_number, options = {})
53
- requires!(options, :username, :password)
54
-
55
- new(options, :username, :password)
56
- end
57
-
58
- def all(&block)
53
+ def all
59
54
  http, request = get_http_and_request(API_URL, '/DailyItemUpdate')
60
55
 
61
56
  request.set_form_data(form_params(@options).merge({
@@ -63,12 +58,32 @@ module SportsSouth
63
58
  LastItem: @options[:last_item].to_s
64
59
  }))
65
60
 
66
- response = http.request(request)
67
- xml_doc = Nokogiri::XML(sanitize_response(response))
61
+ items = []
62
+ tempfile = download_to_tempfile(http, request)
68
63
 
69
- xml_doc.css('Table').map do |item|
70
- yield(map_hash(item, @options[:full_product]))
64
+ tempfile.rewind
65
+
66
+ Nokogiri::XML::Reader.from_io(tempfile).each do |reader|
67
+ next unless reader.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT
68
+ next unless reader.name == ITEM_NODE_NAME
69
+
70
+ node = Nokogiri::XML.parse(reader.outer_xml)
71
+
72
+ _map_hash = map_hash(node.css(ITEM_NODE_NAME), @options[:full_product])
73
+
74
+ items << _map_hash unless _map_hash.nil?
71
75
  end
76
+
77
+ tempfile.close
78
+ tempfile.unlink
79
+
80
+ assign_brand_names(items)
81
+ end
82
+
83
+ def self.get_description(item_number, options = {})
84
+ requires!(options, :username, :password)
85
+
86
+ new(options, :username, :password)
72
87
  end
73
88
 
74
89
  def get_description(item_number)
@@ -87,41 +102,29 @@ module SportsSouth
87
102
  protected
88
103
 
89
104
  def map_hash(node, full_product = false)
90
- category = @categories.find { |category| category[:category_id] == content_for(node, 'CATID') }
91
- brand = @brands.find { |brand| brand[:brand_id] == content_for(node, 'ITBRDNO') }
92
- features = self.map_features(category.except(:category_id, :department_id, :department_description, :description), node)
93
-
94
- model = content_for(node, 'IMODEL')
95
- series = content_for(node, 'SERIES')
96
- mfg_number = content_for(node, 'MFGINO')
97
-
98
- if features[:caliber]
99
- caliber = features[:caliber]
100
- elsif features[:gauge]
101
- caliber = features[:gauge]
102
- end
103
-
104
- if features[:action]
105
- action = features[:action]
106
- end
107
-
108
- if full_product
109
- long_description = self.get_description(content_for(node, 'ITEMNO'))
110
- end
105
+ category = @categories.find { |category| category[:category_id] == content_for(node, 'CATID') }
106
+ features = self.map_features(category.except(:category_id, :department_id, :department_description, :description), node)
107
+ model = content_for(node, 'IMODEL')
108
+ series = content_for(node, 'SERIES')
109
+ mfg_number = content_for(node, 'MFGINO')
110
+ caliber = features[:caliber].presence || features[:gauge].presence
111
+ action = features[:action].presence
112
+ unit_of_measure = UNITS_OF_MEASURE.fetch(content_for(node, 'UOM'), nil)
111
113
 
112
114
  if features.respond_to?(:[]=)
113
115
  features[:series] = series
116
+ features[:unit_of_measure] = unit_of_measure
114
117
  end
115
118
 
116
119
  {
117
- name: "#{model} #{series} #{mfg_number}".gsub(/\s+/, ' ').rstrip,
120
+ name: "#{model} #{series} #{mfg_number}".gsub(/\s+/, ' ').strip,
118
121
  model: model,
119
122
  upc: content_for(node, 'ITUPC').rjust(12, "0"),
120
123
  item_identifier: content_for(node, 'ITEMNO'),
121
124
  quantity: content_for(node, 'QTYOH').to_i,
122
125
  price: content_for(node, 'CPRC'),
123
126
  short_description: content_for(node, 'SHDESC'),
124
- long_description: long_description,
127
+ long_description: (full_product ? get_description(content_for(node, 'ITEMNO')) : nil),
125
128
  category: category[:description],
126
129
  product_type: ITEM_TYPES[content_for(node, 'ITYPE')],
127
130
  mfg_number: mfg_number,
@@ -129,23 +132,23 @@ module SportsSouth
129
132
  caliber: caliber,
130
133
  action: action,
131
134
  map_price: content_for(node, 'MFPRC'),
132
- brand: brand.present? ? brand[:name] : nil,
135
+ brand: content_for(node, 'ITBRDNO').presence,
133
136
  features: features,
134
- unit_of_measure: UNITS_OF_MEASURE.fetch(content_for(node, 'UOM'), nil)
137
+ unit_of_measure: unit_of_measure,
135
138
  }
136
139
  end
137
140
 
138
141
  def map_features(attributes, node)
139
142
  features = {
140
- attributes[:attribute_1] => content_for(node, 'ITATR1'),
141
- attributes[:attribute_2] => content_for(node, 'ITATR2'),
142
- attributes[:attribute_3] => content_for(node, 'ITATR3'),
143
- attributes[:attribute_4] => content_for(node, 'ITATR4'),
144
- attributes[:attribute_5] => content_for(node, 'ITATR5'),
145
- attributes[:attribute_6] => content_for(node, 'ITATR6'),
146
- attributes[:attribute_7] => content_for(node, 'ITATR7'),
147
- attributes[:attribute_8] => content_for(node, 'ITATR8'),
148
- attributes[:attribute_9] => content_for(node, 'ITATR9'),
143
+ attributes[:attribute_1] => content_for(node, 'ITATR1'),
144
+ attributes[:attribute_2] => content_for(node, 'ITATR2'),
145
+ attributes[:attribute_3] => content_for(node, 'ITATR3'),
146
+ attributes[:attribute_4] => content_for(node, 'ITATR4'),
147
+ attributes[:attribute_5] => content_for(node, 'ITATR5'),
148
+ attributes[:attribute_6] => content_for(node, 'ITATR6'),
149
+ attributes[:attribute_7] => content_for(node, 'ITATR7'),
150
+ attributes[:attribute_8] => content_for(node, 'ITATR8'),
151
+ attributes[:attribute_9] => content_for(node, 'ITATR9'),
149
152
  attributes[:attribute_10] => content_for(node, 'ITATR10'),
150
153
  attributes[:attribute_11] => content_for(node, 'ITATR11'),
151
154
  attributes[:attribute_12] => content_for(node, 'ITATR12'),
@@ -164,5 +167,22 @@ module SportsSouth
164
167
  features.symbolize_keys!
165
168
  end
166
169
 
170
+ def assign_brand_names(items)
171
+ brand_ids = items.collect { |item| item[:brand] }.uniq.compact
172
+
173
+ brand_ids.each do |brand_id|
174
+ brand_name = @brands.find { |brand| brand[:brand_id] == brand_id }.try(:[], :name)
175
+
176
+ next if brand_name.nil?
177
+
178
+ items.map! do |item|
179
+ item[:brand] = brand_name if item[:brand] == brand_id
180
+ item
181
+ end
182
+ end
183
+
184
+ items
185
+ end
186
+
167
187
  end
168
188
  end
@@ -2,6 +2,7 @@ module SportsSouth
2
2
  class Inventory < Base
3
3
 
4
4
  API_URL = 'http://webservices.theshootingwarehouse.com/smart/inventory.asmx'
5
+ ITEM_NODE_NAME = 'Onhand'
5
6
 
6
7
  def initialize(options = {})
7
8
  requires!(options, :username, :password)
@@ -18,7 +19,7 @@ module SportsSouth
18
19
  new(options).get_quantity_file
19
20
  end
20
21
 
21
- def self.all(options = {}, &block)
22
+ def self.all(options = {})
22
23
  requires!(options, :username, :password)
23
24
 
24
25
  if options[:last_updated].present?
@@ -29,7 +30,7 @@ module SportsSouth
29
30
 
30
31
  options[:last_item] ||= '-1'
31
32
 
32
- new(options).all(&block)
33
+ new(options).all
33
34
  end
34
35
 
35
36
  def self.get(item_identifier, options = {})
@@ -37,7 +38,7 @@ module SportsSouth
37
38
  new(options).get(item_identifier)
38
39
  end
39
40
 
40
- def all(&block)
41
+ def all
41
42
  http, request = get_http_and_request(API_URL, '/IncrementalOnhandUpdate')
42
43
 
43
44
  request.set_form_data(form_params = form_params(@options).merge({
@@ -45,12 +46,26 @@ module SportsSouth
45
46
  LastItem: @options[:last_item].to_s
46
47
  }))
47
48
 
48
- response = http.request(request)
49
- xml_doc = Nokogiri::XML(sanitize_response(response))
49
+ items = []
50
+ tempfile = download_to_tempfile(http, request)
50
51
 
51
- xml_doc.css('Onhand').map do |item|
52
- yield(self.map_hash(item))
52
+ tempfile.rewind
53
+
54
+ Nokogiri::XML::Reader.from_io(tempfile).each do |reader|
55
+ next unless reader.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT
56
+ next unless reader.name == ITEM_NODE_NAME
57
+
58
+ node = Nokogiri::XML.parse(reader.outer_xml)
59
+
60
+ _map_hash = map_hash(node.css(ITEM_NODE_NAME))
61
+
62
+ items << _map_hash unless _map_hash.nil?
53
63
  end
64
+
65
+ tempfile.close
66
+ tempfile.unlink
67
+
68
+ items
54
69
  end
55
70
 
56
71
  def get_quantity_file
@@ -73,7 +88,7 @@ module SportsSouth
73
88
  tempfile.path
74
89
  end
75
90
 
76
- def self.quantity(options = {}, &block)
91
+ def self.quantity(options = {})
77
92
  requires!(options, :username, :password)
78
93
 
79
94
  if options[:last_updated].present?
@@ -84,7 +99,7 @@ module SportsSouth
84
99
 
85
100
  options[:last_item] ||= '-1'
86
101
 
87
- new(options).all(&block)
102
+ new(options).all
88
103
  end
89
104
 
90
105
  def get(item_identifier)
@@ -1,3 +1,3 @@
1
1
  module SportsSouth
2
- VERSION = '3.0.2'.freeze
2
+ VERSION = '5.0.1'.freeze
3
3
  end
@@ -4,8 +4,6 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'sports_south/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.required_ruby_version = '~> 2.0'
8
-
9
7
  spec.name = "sports_south"
10
8
  spec.version = SportsSouth::VERSION
11
9
  spec.authors = ["Dale Campbell"]
@@ -22,10 +20,10 @@ Gem::Specification.new do |spec|
22
20
 
23
21
  spec.add_dependency "nokogiri", "~> 1.6"
24
22
 
25
- spec.add_development_dependency "bundler", "~> 1.12"
26
23
  spec.add_development_dependency "activesupport", "~> 5"
24
+ spec.add_development_dependency "bundler", "~> 1.12"
27
25
  spec.add_development_dependency "net-http-spy", "~> 0.2"
28
- spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rake", ">= 12.3.3"
29
27
  spec.add_development_dependency "rspec", "~> 3.3"
30
28
  spec.add_development_dependency "webmock", "~> 1.20"
31
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sports_south
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 5.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dale Campbell
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-17 00:00:00.000000000 Z
11
+ date: 2020-07-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -25,33 +25,33 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.6'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.12'
33
+ version: '5'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.12'
40
+ version: '5'
41
41
  - !ruby/object:Gem::Dependency
42
- name: activesupport
42
+ name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '5'
47
+ version: '1.12'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '5'
54
+ version: '1.12'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: net-http-spy
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -70,16 +70,16 @@ dependencies:
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '10.0'
75
+ version: 12.3.3
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '10.0'
82
+ version: 12.3.3
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rspec
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -115,11 +115,11 @@ executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
+ - ".circleci/config.yml"
118
119
  - ".gitignore"
119
120
  - ".rspec"
120
121
  - ".ruby-gemset"
121
122
  - ".ruby-version"
122
- - ".travis.yml"
123
123
  - Gemfile
124
124
  - LICENSE.txt
125
125
  - README.md
@@ -150,9 +150,9 @@ require_paths:
150
150
  - lib
151
151
  required_ruby_version: !ruby/object:Gem::Requirement
152
152
  requirements:
153
- - - "~>"
153
+ - - ">="
154
154
  - !ruby/object:Gem::Version
155
- version: '2.0'
155
+ version: '0'
156
156
  required_rubygems_version: !ruby/object:Gem::Requirement
157
157
  requirements:
158
158
  - - ">="
@@ -160,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
160
  version: '0'
161
161
  requirements: []
162
162
  rubyforge_project:
163
- rubygems_version: 2.6.12
163
+ rubygems_version: 2.6.14.1
164
164
  signing_key:
165
165
  specification_version: 4
166
166
  summary: Sports South API Ruby library.
@@ -1,4 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.0
4
- before_install: gem install bundler -v 1.10.6