packwizard-parser 1.0.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.
- checksums.yaml +7 -0
- data/README.md +144 -0
- data/lib/packwizard_parser/category.rb +31 -0
- data/lib/packwizard_parser/category_parser.rb +60 -0
- data/lib/packwizard_parser/gram_converter.rb +24 -0
- data/lib/packwizard_parser/item.rb +71 -0
- data/lib/packwizard_parser/item_parser.rb +98 -0
- data/lib/packwizard_parser/list.rb +31 -0
- data/lib/packwizard_parser/list_parser.rb +37 -0
- data/lib/packwizard_parser/parser.rb +40 -0
- data/lib/packwizard_parser/version.rb +5 -0
- data/lib/packwizard_parser.rb +24 -0
- data/packwizard-parser.gemspec +28 -0
- data/spec/category_parser_spec.rb +38 -0
- data/spec/fixtures/negative.json +71 -0
- data/spec/fixtures/tUE6BJs.json +677 -0
- data/spec/item_parser_spec.rb +191 -0
- data/spec/list_parser_spec.rb +50 -0
- data/spec/parser_spec.rb +52 -0
- data/spec/spec_helper.rb +18 -0
- metadata +92 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe PackwizardParser::ItemParser do
|
|
4
|
+
let(:fixture_json) { File.read(File.join(__dir__, 'fixtures', 'tUE6BJs.json')) }
|
|
5
|
+
let(:data) { JSON.parse(fixture_json) }
|
|
6
|
+
let(:parser) { described_class.new }
|
|
7
|
+
|
|
8
|
+
describe '#parse' do
|
|
9
|
+
context 'with Plex Solo Tent row. Category: Big 3' do
|
|
10
|
+
let(:category) { data['tableData'].find { |c| c['title'] == 'Big 3' } }
|
|
11
|
+
let(:rows) { category['rows'] }
|
|
12
|
+
let(:row) { rows['row_lhkykmnb'] }
|
|
13
|
+
|
|
14
|
+
it 'extracts the item name' do
|
|
15
|
+
item = parser.parse(row)
|
|
16
|
+
expect(item.name).to eq('Plex Solo Tent')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'extracts the item weight' do
|
|
20
|
+
item = parser.parse(row)
|
|
21
|
+
expect(item.weight).to eq(394)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'extracts the item quantity' do
|
|
25
|
+
item = parser.parse(row)
|
|
26
|
+
expect(item.quantity).to eq(1)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'extracts the imageUrl' do
|
|
30
|
+
item = parser.parse(row)
|
|
31
|
+
expect(item.image_url).to eq('https://zpacks.com/cdn/shop/files/plexsoloclassic_2048x.jpg?v=1713272032')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'extracts the description' do
|
|
35
|
+
item = parser.parse(row)
|
|
36
|
+
expect(item.description).to include('The lightest of the light')
|
|
37
|
+
expect(item.description).to end_with('back and side sleepers alike.')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'extracts the consumable flag: False' do
|
|
41
|
+
item = parser.parse(row)
|
|
42
|
+
expect(item.consumable).to be(false)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'extracts the worn flag: False' do
|
|
46
|
+
item = parser.parse(row)
|
|
47
|
+
expect(item.worn).to be(false)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'extracts the total_weight' do
|
|
51
|
+
item = parser.parse(row)
|
|
52
|
+
expect(item.total_weight).to eq(394)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'extracts the total_quantity' do
|
|
56
|
+
item = parser.parse(row)
|
|
57
|
+
expect(item.quantity).to eq(1)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'extracts the total_consumable_weight' do
|
|
61
|
+
item = parser.parse(row)
|
|
62
|
+
expect(item.total_consumable_weight).to eq(nil)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'extracts the total_worn_weight' do
|
|
66
|
+
item = parser.parse(row)
|
|
67
|
+
expect(item.total_worn_weight).to eq(0)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# These testcases should check if the weight conversion of ounces to grams works
|
|
72
|
+
# Also if the consumable flag works
|
|
73
|
+
|
|
74
|
+
context 'with Snackbars row. Category: Cook System' do
|
|
75
|
+
let(:category) { data['tableData'].find { |c| c['title'] == 'Cook System' } }
|
|
76
|
+
let(:rows) { category['rows'] }
|
|
77
|
+
let(:row) { rows['row_mnfyxjad'] }
|
|
78
|
+
|
|
79
|
+
it 'extracts consumable flag: True' do
|
|
80
|
+
item = parser.parse(row)
|
|
81
|
+
expect(item.consumable).to be(true)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'extracts the item weight and converts oz to grams' do
|
|
85
|
+
# item weight in oz = 1, should equal 28.35 grams
|
|
86
|
+
item = parser.parse(row)
|
|
87
|
+
expect(item.weight).to be_within(0.01).of(28.35)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'extracts the item_total_weight and converts oz to grams' do
|
|
91
|
+
# item weight in oz = 1, quantity 4, should equal total_weight of 113.4 grams
|
|
92
|
+
item = parser.parse(row)
|
|
93
|
+
expect(item.total_weight).to be_within(0.01).of(113.4)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'extracts the total_consumable_weight and converts oz to grams' do
|
|
97
|
+
# item weight in oz = 1, quantity 4, should equal total_consumable_weight of 113.4 grams
|
|
98
|
+
item = parser.parse(row)
|
|
99
|
+
expect(item.total_consumable_weight).to be_within(0.01).of(113.4)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe 'with Socks row. Category: Clothing' do
|
|
104
|
+
let(:category) { data['tableData'].find { |c| c['title'] == 'Clothing' } }
|
|
105
|
+
let(:rows) { category['rows'] }
|
|
106
|
+
let(:row) { rows['row_lhqtz6q5'] }
|
|
107
|
+
|
|
108
|
+
it 'extracts quantity' do
|
|
109
|
+
item = parser.parse(row)
|
|
110
|
+
expect(item.quantity).to eq(3)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'extracts weight and converts lb to grams' do
|
|
114
|
+
# item weight in lb 0.08, should equal to 36.29 grams
|
|
115
|
+
item = parser.parse(row)
|
|
116
|
+
expect(item.weight).to be_within(0.01).of(36.29)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'extracts the item_total_weight and converts lb to grams' do
|
|
120
|
+
# Quantity of 3, should equal to 108.87 grams
|
|
121
|
+
item = parser.parse(row)
|
|
122
|
+
expect(item.total_weight).to be_within(0.01).of(108.87)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'extracts the worn flag: True' do
|
|
126
|
+
item = parser.parse(row)
|
|
127
|
+
expect(item.worn).to be(true)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'extracts the worn_quantity, should be 1 regardless of quantity' do
|
|
131
|
+
item = parser.parse(row)
|
|
132
|
+
expect(item.worn_quantity).to eq(1)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'extracts the total_worn_weight and converts lb to grams' do
|
|
136
|
+
item = parser.parse(row)
|
|
137
|
+
expect(item.total_worn_weight).to be_within(0.01).of(36.29)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
describe 'with Decathlon Fleece pants row. Category: Clothing' do
|
|
142
|
+
let(:category) { data['tableData'].find { |c| c['title'] == 'Clothing' } }
|
|
143
|
+
let(:rows) { category['rows'] }
|
|
144
|
+
let(:row) { rows['row_ljhklzt7'] }
|
|
145
|
+
|
|
146
|
+
it 'extracts the weight and converts kg to grams' do
|
|
147
|
+
item = parser.parse(row)
|
|
148
|
+
expect(item.weight).to be_within(0.01).of(149)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe 'negative testcases with negative.json' do
|
|
153
|
+
let(:fixture_json) { File.read(File.join(__dir__, 'fixtures', 'negative.json')) }
|
|
154
|
+
let(:data) { JSON.parse(fixture_json) }
|
|
155
|
+
let(:parser) { described_class.new }
|
|
156
|
+
let(:category) { data['tableData'].first }
|
|
157
|
+
let(:rows) { category['rows'] }
|
|
158
|
+
let(:row) { rows['row_negative'] }
|
|
159
|
+
|
|
160
|
+
it 'negative weight should return 0.0' do
|
|
161
|
+
item = parser.parse(row)
|
|
162
|
+
expect(item.weight).to eq(0.0)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'negative quantity should return 0' do
|
|
166
|
+
item = parser.parse(row)
|
|
167
|
+
expect(item.quantity).to eq(0)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'unknown unit type (xyz) should return 0.0' do
|
|
171
|
+
item = parser.parse(row)
|
|
172
|
+
expect(item.weight).to eq(0.0)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'empty name should return Untitled item' do
|
|
176
|
+
item = parser.parse(row)
|
|
177
|
+
expect(item.name).to eq('Untitled item')
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'empty description should return nil' do
|
|
181
|
+
item = parser.parse(row)
|
|
182
|
+
expect(item.description).to be_nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'empty url should return nil' do
|
|
186
|
+
item = parser.parse(row)
|
|
187
|
+
expect(item.image_url).to be_nil
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe PackwizardParser::ListParser do
|
|
4
|
+
let(:fixture_json) { File.read(File.join(__dir__, 'fixtures', 'tUE6BJs.json')) }
|
|
5
|
+
let(:data) { JSON.parse(fixture_json) }
|
|
6
|
+
let(:parser) { described_class.new }
|
|
7
|
+
let(:item_parser) { PackwizardParser::ItemParser.new }
|
|
8
|
+
let(:category_parser) { PackwizardParser::CategoryParser.new }
|
|
9
|
+
|
|
10
|
+
describe '#parse' do
|
|
11
|
+
let(:list) { parser.parse(data, category_parser: category_parser, item_parser: item_parser) }
|
|
12
|
+
|
|
13
|
+
it 'extracts list name' do
|
|
14
|
+
expect(list.name).to eq('Ultralight Gear List 2023')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'extracts the lists description' do
|
|
18
|
+
expect(list.description).to end_with('but this pack comes close!')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'returns an array of Category objects' do
|
|
22
|
+
expect(list.categories).to be_a(Array)
|
|
23
|
+
expect(list.categories.length).to be > 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'returns an array of Item objects' do
|
|
27
|
+
first_category = list.categories.first
|
|
28
|
+
expect(first_category.items).to be_a(Array)
|
|
29
|
+
expect(first_category.items.length).to be > 0
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#negative cases with negative.json' do
|
|
34
|
+
let(:fixture_json) { File.read(File.join(__dir__, 'fixtures', 'negative.json')) }
|
|
35
|
+
let(:data) { JSON.parse(fixture_json) }
|
|
36
|
+
let(:parser) { described_class.new }
|
|
37
|
+
let(:item_parser) { PackwizardParser::ItemParser.new }
|
|
38
|
+
let(:category_parser) { PackwizardParser::CategoryParser.new }
|
|
39
|
+
|
|
40
|
+
let(:list) { parser.parse(data, category_parser: category_parser, item_parser: item_parser) }
|
|
41
|
+
|
|
42
|
+
it 'extract empty list name' do
|
|
43
|
+
expect(list.name).to eq('Untitled list')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'extract empty description' do
|
|
47
|
+
expect(list.description).to be_nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/spec/parser_spec.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe PackwizardParser::Parser do
|
|
4
|
+
let(:fixture_json) { File.read(File.join(__dir__, 'fixtures', 'tUE6BJs.json')) }
|
|
5
|
+
let(:fake_response) do
|
|
6
|
+
instance_double(HTTParty::Response, success?: true, body: fixture_json, code: 200)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
allow(HTTParty).to receive(:get).and_return(fake_response)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "#parse" do
|
|
14
|
+
subject(:list) { described_class.new(shareable_id: 'tUE6BJs').parse }
|
|
15
|
+
|
|
16
|
+
it 'returns a List object' do
|
|
17
|
+
expect(list).to be_a(PackwizardParser::List)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'extracts the list name' do
|
|
21
|
+
expect(list.name).to eq('Ultralight Gear List 2023')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'extracts the list description' do
|
|
25
|
+
expect(list.description).to end_with('but this pack comes close!')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'accepts a full URL and extracts the ID' do
|
|
29
|
+
parser = described_class.new(shareable_id: 'https://www.packwizard.com/s/tUE6BJs')
|
|
30
|
+
parser.parse
|
|
31
|
+
expect(HTTParty).to have_received(:get).with(
|
|
32
|
+
PackwizardParser::Parser::API_URL,
|
|
33
|
+
hash_including(query: { shareableId: 'tUE6BJs' }),
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'calls the API with the correct shareable ID' do
|
|
38
|
+
list
|
|
39
|
+
expect(HTTParty).to have_received(:get).with(
|
|
40
|
+
PackwizardParser::Parser::API_URL,
|
|
41
|
+
hash_including(query: { shareableId: 'tUE6BJs' })
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'raises a failed response' do
|
|
46
|
+
allow(HTTParty).to receive(:get).and_return(
|
|
47
|
+
instance_double(HTTParty::Response, success?: false, code: 404),
|
|
48
|
+
)
|
|
49
|
+
expect { described_class.new(shareable_id: "bad").parse }.to raise_error(RuntimeError, /404/)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/packwizard_parser/'
|
|
4
|
+
|
|
5
|
+
RSpec.configure do |config|
|
|
6
|
+
# Enable flags like --only-failures and --next-failure
|
|
7
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
|
8
|
+
|
|
9
|
+
# Exclude integration testing as default
|
|
10
|
+
config.filter_run_excluding :integration
|
|
11
|
+
|
|
12
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
|
13
|
+
config.disable_monkey_patching!
|
|
14
|
+
|
|
15
|
+
config.expect_with :rspec do |c|
|
|
16
|
+
c.syntax = :expect
|
|
17
|
+
end
|
|
18
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: packwizard-parser
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Aross AB / Goulight
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-05-11 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: httparty
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.24'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.24'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.13'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.13'
|
|
41
|
+
description: Parse PackWizard to extract list data including categories, items, weights,
|
|
42
|
+
and metadata
|
|
43
|
+
email:
|
|
44
|
+
- hello@goulight.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- README.md
|
|
50
|
+
- lib/packwizard_parser.rb
|
|
51
|
+
- lib/packwizard_parser/category.rb
|
|
52
|
+
- lib/packwizard_parser/category_parser.rb
|
|
53
|
+
- lib/packwizard_parser/gram_converter.rb
|
|
54
|
+
- lib/packwizard_parser/item.rb
|
|
55
|
+
- lib/packwizard_parser/item_parser.rb
|
|
56
|
+
- lib/packwizard_parser/list.rb
|
|
57
|
+
- lib/packwizard_parser/list_parser.rb
|
|
58
|
+
- lib/packwizard_parser/parser.rb
|
|
59
|
+
- lib/packwizard_parser/version.rb
|
|
60
|
+
- packwizard-parser.gemspec
|
|
61
|
+
- spec/category_parser_spec.rb
|
|
62
|
+
- spec/fixtures/negative.json
|
|
63
|
+
- spec/fixtures/tUE6BJs.json
|
|
64
|
+
- spec/item_parser_spec.rb
|
|
65
|
+
- spec/list_parser_spec.rb
|
|
66
|
+
- spec/parser_spec.rb
|
|
67
|
+
- spec/spec_helper.rb
|
|
68
|
+
homepage: https://github.com/goulight/packwizard-parser
|
|
69
|
+
licenses:
|
|
70
|
+
- MIT
|
|
71
|
+
metadata:
|
|
72
|
+
source_code_uri: https://github.com/goulight/packwizard-parser
|
|
73
|
+
post_install_message:
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.0'
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 3.5.22
|
|
89
|
+
signing_key:
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: Parser for PackWizard lists
|
|
92
|
+
test_files: []
|