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.
@@ -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
@@ -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
@@ -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: []