danger-app_size_report 0.0.1 → 0.0.3

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.
@@ -1,111 +1,185 @@
1
- require 'json'
2
- require 'set'
1
+ # frozen_string_literal: false
3
2
 
4
3
  module Danger
5
- # Validates variant sizes of app thinning report json within a Pull Request and generates a brief report.
4
+ require 'json'
5
+ require_relative '../converter/parser/report_parser'
6
+ require_relative '../converter/helper/memory_size'
7
+
8
+ # A Danger plugin for reporting iOS app size violations.
9
+ # A valid App Thinning Size Report must be passed to the plugin
10
+ # for accurate functionality.
11
+ #
12
+ # @example Report app size violations if one or more App variants
13
+ # exceed 4GB.
14
+ #
15
+ # report_path = "/Path/to/AppSize/Report.txt"
16
+ # app_size_report.flag_violations(
17
+ # report_path,
18
+ # build_type: 'App',
19
+ # size_limit: 4,
20
+ # limit_unit: 'GB',
21
+ # fail_on_warning: false
22
+ # )
23
+ #
24
+ # @example Report app size violations if one or more App Clip variants
25
+ # exceed 8MB.
6
26
  #
7
- # @example Validating code coverage for app_th (easy-peasy.io)
27
+ # report_path = "/Path/to/AppSize/Report.txt"
28
+ # app_size_report.flag_violations(
29
+ # report_path,
30
+ # build_type: 'Clip',
31
+ # size_limit: 8,
32
+ # limit_unit: 'MB',
33
+ # fail_on_warning: false
34
+ # )
8
35
  #
9
- # # Get the path to your app thinning report
10
- #
11
- # # The result is sent to the pull request with a markdown format and
12
- # # notifies failure which variants is violating the size limit.
36
+ # @example Fail PR if one or more App Clip variants exceed 8MB.
13
37
  #
14
- # app_size_report.report(
15
- # [path_to_report],
16
- # [app_size_limit]
17
- # )
38
+ # report_path = "/Path/to/AppSize/Report.txt"
39
+ # app_size_report.flag_violations(
40
+ # report_path,
41
+ # build_type: 'Clip',
42
+ # size_limit: 8,
43
+ # limit_unit: 'MB',
44
+ # fail_on_warning: true
45
+ # )
46
+ #
47
+ # @example Get JSON string representation of app thinning size report
48
+ #
49
+ # report_path = "/Path/to/AppSize/Report.txt"
50
+ # app_size_json = app_size_report.report_json(report_path)
51
+ #
52
+ # @see ChargePoint/danger-app_size_report
53
+ # @tags ios, xcode, appclip, thinning, size
18
54
  #
19
55
  class DangerAppSizeReport < Plugin
20
- # A method that create app thinning report
21
- # @return [Array<Variant>]
56
+ # Reports app size violations given a valid App Thinning Size Report.
57
+ # @param [String, required] report_path
58
+ # Path to valid App Thinning Size Report text file.
59
+ # @param [String, optional] build_type
60
+ # Specify whether the report corresponds to an App or an App Clip.
61
+ # Default: 'App'
62
+ # Supported values: 'App', 'Clip'
63
+ # @param [Numeric, optional] size_limit
64
+ # Specify the app size limit.
65
+ # Default: 4
66
+ # @param [String, optional] limit_unit
67
+ # Specific the unit for the given size limit.
68
+ # Default: 'GB'
69
+ # Supported values: 'KB', 'MB', 'GB'
70
+ # @param [Boolean, optional] fail_on_warning
71
+ # Specify whether the PR should fail if one or more app variants
72
+ # exceed the given size limit. By default, the plugin issues
73
+ # a warning in this case.
74
+ # Default: 'false'
75
+ # @return [void]
22
76
  #
23
- def report(report_path, app_size_limit)
24
- if File.exist?(report_path)
25
- report = File.read(report_path)
26
- json = JSON.parse(report)
27
-
28
- flagged_variants = parse_json(*json, app_size_limit)
29
-
30
- if flagged_variants.length > 0
31
- failure "The App Clip size limit of 10 MB has been exceeded by one or more variants"
32
- end
77
+ def flag_violations(report_path, build_type: 'App', size_limit: 4, limit_unit: 'GB', fail_on_warning: false)
78
+ report_text = File.read(report_path)
79
+ variants = ReportParser.parse(report_text)
33
80
 
34
- init_app_size_violation_table(*json, flagged_variants, app_size_limit)
35
- init_supported_variant_descriptors(*json)
36
- init_ads()
81
+ unless %w[App Clip].include? build_type
82
+ raise ArgumentError, "The 'build_type' argument only accepts the values \"App\" and \"Clip\""
37
83
  end
38
- end
39
84
 
40
- # A method that create an array of variants that violate app size limit
41
- # @return [Array<Variant>]
42
- #
43
- def parse_json(*json, app_size_limit)
44
- flagged_variants = Array.new
45
-
46
- json.each do |variant, value|
47
- app_size = variant["app_size"]["uncompressed"]["value"]
48
- app_odr_size = variant["app_on_demand_resources_size"]["uncompressed"]["value"]
49
- if app_size > app_size_limit || app_odr_size > app_size_limit
50
- flagged_variants.append(variant)
51
- end
85
+ raise ArgumentError, "The 'size_limit' argument only accepts numeric values" unless size_limit.is_a? Numeric
86
+
87
+ limit_unit.upcase!
88
+ unless %w[KB MB GB].include? limit_unit
89
+ raise ArgumentError, "The 'build_type' argument only accepts the values \"KB\", \"MB\" and \"GB\""
52
90
  end
53
-
54
- if flagged_variants.length > 0
55
- failure "The App Clip size limit of 10 MB has been exceeded by one or more variants"
91
+
92
+ unless [true, false].include? fail_on_warning
93
+ raise ArgumentError, "The 'fail_on_warning' argument only accepts the values 'true' and 'false'"
56
94
  end
57
95
 
58
- return flagged_variants
96
+ generate_size_report_markdown(variants, build_type, size_limit, limit_unit, fail_on_warning)
97
+ generate_variant_descriptors_markdown(variants)
98
+ generate_ads_label_markdown
59
99
  end
60
100
 
61
- # A method that create the violation table in Github
62
- #
101
+ # Returns a JSON string representation of the given App Thinning Size Report.
102
+ # @param [String, required] report_path
103
+ # Path to valid App Thinning Size Report text file.
104
+ # @return [String]
63
105
  #
64
- def init_app_size_violation_table(*json, flagged_variants, app_size_limit)
106
+ def report_json(report_path)
107
+ report_text = File.read(report_path)
108
+ variants = ReportParser.parse(report_text)
109
+ JSON.pretty_generate(variants)
110
+ end
111
+
112
+ private
113
+
114
+ def generate_size_report_markdown(variants, build_type, size_limit, limit_unit, fail_on_warning)
115
+ limit_size = MemorySize.new("#{size_limit}#{limit_unit}")
116
+
117
+ if build_type == 'Clip' && limit_size.megabytes > 10
118
+ message "The size limit was set to 10 MB as the given limit of #{size_limit} #{limit_unit} exceeds Apple's App Clip size restrictions"
119
+ size_limit = 10
120
+ limit_unit = 'MB'
121
+ limit_size.kilobytes = 10 * 1024
122
+ elsif build_type == 'App' && limit_size.gigabytes > 4
123
+ message "The size limit was set to 4 GB as the given limit of #{size_limit} #{limit_unit} exceeds Apple's App size restrictions"
124
+ size_limit = 4
125
+ limit_unit = 'GB'
126
+ limit_size.kilobytes = 4 * 1024 * 1024
127
+ end
128
+
129
+ flagged_variant_names = []
130
+ variants.each do |variant|
131
+ if variant.app_size.uncompressed.value > limit_size.megabytes || variant.on_demand_resources_size.uncompressed.value > limit_size.megabytes
132
+ flagged_variant_names.append(variant.variant)
133
+ end
134
+ end
135
+
136
+ if flagged_variant_names.length.positive?
137
+ if fail_on_warning
138
+ failure "The size limit of #{size_limit} #{limit_unit.upcase} has been exceeded by one or more variants"
139
+ else
140
+ warn "The size limit of #{size_limit} #{limit_unit.upcase} has been exceeded by one or more variants"
141
+ end
142
+ end
143
+
65
144
  size_report = "# App Thinning Size Report\n"
66
- size_report << "### Size limit = #{app_size_limit} MB\n\n"
145
+ size_report << "### Size limit = #{size_limit} #{limit_unit.upcase}\n\n"
67
146
  size_report << "| Under Limit | Variant | App Size - Compressed | App Size - Uncompressed | ODR Size - Compressed | ODR Size - Uncompressed |\n"
68
- size_report << "| ---------- | ------- | --------------------- | --------------------- | --------------------- | --------------------- |\n"
69
- flagged_variants_set = flagged_variants.to_set
70
- json.each do |variant, _|
71
- isViolating = flagged_variants_set.include?(variant) ? "❌" : "✅"
72
- app_size_compressed = "#{variant["app_size"]["compressed"]["value"]} #{variant["app_size"]["uncompressed"]["unit"]}"
73
- app_size_uncompressed = "#{variant["app_size"]["uncompressed"]["value"]} #{variant["app_size"]["uncompressed"]["unit"]}"
74
- odr_size_compressed = "#{variant["on_demand_resources_size"]["compressed"]["value"]} #{variant["app_size"]["uncompressed"]["unit"]}"
75
- odr_size_uncompressed = "#{variant["on_demand_resources_size"]["uncompressed"]["value"]} #{variant["app_size"]["uncompressed"]["unit"]}"
76
-
77
- size_report << "#{isViolating} | #{variant["variant"]} | #{app_size_compressed} | #{app_size_uncompressed} | #{odr_size_compressed} | #{odr_size_uncompressed} |\n"
147
+ size_report << "| :-: | :-: | :-: | :-: | :-: | :-: |\n"
148
+
149
+ flagged_variants_set = flagged_variant_names.to_set
150
+
151
+ variants.each do |variant|
152
+ is_violating = flagged_variants_set.include?(variant.variant) ? '❌' : '✅'
153
+ app_size_compressed = "#{variant.app_size.compressed.value} #{variant.app_size.compressed.unit}"
154
+ app_size_uncompressed = "#{variant.app_size.uncompressed.value} #{variant.app_size.uncompressed.unit}"
155
+ odr_size_compressed = "#{variant.on_demand_resources_size.compressed.value} #{variant.on_demand_resources_size.compressed.unit}"
156
+ odr_size_uncompressed = "#{variant.on_demand_resources_size.uncompressed.value} #{variant.on_demand_resources_size.uncompressed.unit}"
157
+
158
+ size_report << "#{is_violating} | #{variant.variant} | #{app_size_compressed} | #{app_size_uncompressed} | #{odr_size_compressed} | #{odr_size_uncompressed} |\n"
78
159
  end
79
160
 
80
161
  markdown size_report
81
162
  end
82
163
 
83
- # A method that create the supported variant table in Github
84
- #
85
- #
86
- def init_supported_variant_descriptors(*json)
87
- variant_descriptor_report = "### Supported Variant Descriptors \n\n"
88
- json.each do |variant, _|
89
- variant_descriptor_report << "<details> \n"
90
- variant_descriptor_report << "<summary> Variant #{variant["variant"]} </summary> \n\n"
91
- variant_descriptor_report << "| Model | Operating System | \n"
92
- variant_descriptor_report << "| ----- | ---------------- | \n"
93
- variant["supported_variant_descriptors"].each do |model, _|
94
- variant_descriptor_report << "#{model["device"]} | #{model["os_version"]} | \n"
164
+ def generate_variant_descriptors_markdown(variants)
165
+ variant_descriptors_report = "### Supported Variant Descriptors \n\n"
166
+ variants.each do |variant|
167
+ variant_descriptors_report << "<details> \n"
168
+ variant_descriptors_report << "<summary> #{variant.variant} </summary> \n\n"
169
+ variant_descriptors_report << "| Model | Operating System | \n"
170
+ variant_descriptors_report << "| - | :-: |\n"
171
+ variant.supported_variant_descriptors.each do |variant_descriptor|
172
+ variant_descriptors_report << "#{variant_descriptor.device} | #{variant_descriptor.os_version} | \n"
95
173
  end
96
- variant_descriptor_report << "</details> \n\n"
174
+ variant_descriptors_report << "</details> \n\n"
97
175
  end
98
176
 
99
- markdown variant_descriptor_report
177
+ markdown variant_descriptors_report
100
178
  end
101
179
 
102
- # A method that create the footnote label in Github
103
- #
104
- #
105
- def init_ads
106
- ads_label = "Powered by [danger-app_size_report](https://github.com/ChargePoint)"
180
+ def generate_ads_label_markdown
181
+ ads_label = 'Powered by [danger-app_size_report](https://github.com/ChargePoint)'
107
182
  markdown ads_label
108
183
  end
109
-
110
184
  end
111
185
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ class JSONConverter
6
+ def to_json(_options = {})
7
+ hash = {}
8
+ instance_variables.each do |var|
9
+ key = var.to_s[1..]
10
+ hash[key] = instance_variable_get(var)
11
+ end
12
+ JSON.pretty_generate(hash)
13
+ end
14
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: false
2
+
3
+ require_relative '../helper/json_converter'
4
+ class MemorySize < JSONConverter
5
+ attr_accessor :kilobytes
6
+
7
+ ZERO_SIZE = 'zero kb'.freeze
8
+
9
+ UNIT = {
10
+ bytes: 'B',
11
+ kilobytes: 'KB',
12
+ megabytes: 'MB',
13
+ gigabytes: 'GB'
14
+ }.freeze
15
+
16
+ def bytes
17
+ @kilobytes * 1024
18
+ end
19
+
20
+ def megabytes
21
+ @kilobytes / 1024
22
+ end
23
+
24
+ def gigabytes
25
+ @kilobytes / 1024 / 1024
26
+ end
27
+
28
+ def initialize(text)
29
+ super()
30
+ value = parse_from(text)
31
+
32
+ @kilobytes = value || 0
33
+ end
34
+
35
+ private
36
+
37
+ def parse_from(text)
38
+ text_to_memory_unit = {
39
+ 'b' => :bytes,
40
+ 'byte' => :bytes,
41
+ 'bytes' => :bytes,
42
+ 'kb' => :kilobytes,
43
+ 'kilobyte' => :kilobytes,
44
+ 'kilobytes' => :kilobytes,
45
+ 'mb' => :megabytes,
46
+ 'megabyte' => :megabytes,
47
+ 'megabytes' => :megabytes,
48
+ 'gb' => :gigabytes,
49
+ 'gigabyte' => :gigabytes,
50
+ 'gigabytes' => :gigabytes
51
+ }
52
+
53
+ unit = text_to_memory_unit[parse_units(text)]
54
+ size = parse_size(text)
55
+
56
+ return nil unless size
57
+
58
+ unit ||= :megabytes
59
+
60
+ case unit
61
+ when :bytes
62
+ kilobytes_from_bytes(size)
63
+ when :kilobytes
64
+ size
65
+ when :megabytes
66
+ kilobytes_from_megabytes(size)
67
+ when :gigabytes
68
+ kilobytes_from_gigabytes(size)
69
+ end
70
+ end
71
+
72
+ def parse_units(text)
73
+ return 'kb' if text.downcase == ZERO_SIZE
74
+
75
+ result = ''
76
+
77
+ text.each_char do |char|
78
+ result << char if char.match?(/[[:alpha:]]/) && char != '.' && char != ','
79
+ end
80
+
81
+ result.downcase
82
+ end
83
+
84
+ def parse_size(text)
85
+ return 0.to_f if text.downcase == ZERO_SIZE
86
+
87
+ result = ''
88
+
89
+ text.each_char do |char|
90
+ result << char if char.match?(/[[:digit:]]/) || char == '.' || char == ','
91
+ end
92
+
93
+ result.to_f
94
+ end
95
+
96
+ def kilobytes_from_bytes(bytes)
97
+ bytes / 1024
98
+ end
99
+
100
+ def kilobytes_from_megabytes(megabytes)
101
+ megabytes * 1024
102
+ end
103
+
104
+ def kilobytes_from_gigabytes(gigabytes)
105
+ gigabytes * 1024 * 1024
106
+ end
107
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../parser/variant_parser'
4
+ require_relative '../parser/variant_descriptor_parser'
5
+ require_relative '../parser/app_size_parser'
6
+
7
+ class ResultFactory
8
+ def self.parse(from_text: '', parser: nil)
9
+ result = nil
10
+ case parser
11
+ when :variant
12
+ variant_parser = VariantParser.new(from_text)
13
+ variant_parser.parse
14
+ result = variant_parser.result
15
+ when :supported_variant_descriptors
16
+ variant_descriptor_parser = VariantDescriptorParser.new(from_text)
17
+ variant_descriptor_parser.parse
18
+ result = variant_descriptor_parser.result
19
+ when :app_on_demand_resources_size
20
+ app_size_parser = AppSizeParser.new(from_text)
21
+ app_size_parser.parse
22
+ result = app_size_parser.result
23
+ when :app_size
24
+ app_size_parser = AppSizeParser.new(from_text)
25
+ app_size_parser.parse
26
+ result = app_size_parser.result
27
+ when :on_demand_resources_size
28
+ app_size_parser = AppSizeParser.new(from_text)
29
+ app_size_parser.parse
30
+ result = app_size_parser.result
31
+ end
32
+ result
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helper/json_converter'
4
+ class AppSizeModel < JSONConverter
5
+ attr_reader :compressed, :uncompressed
6
+
7
+ PARSING_KEYS = {
8
+ compressed: 'compressed',
9
+ uncompressed: 'uncompressed'
10
+ }.freeze
11
+
12
+ def initialize(compressed: SizeModel.placeholder, uncompressed: SizeModel.placeholder)
13
+ super()
14
+ @compressed = compressed
15
+ @uncompressed = uncompressed
16
+ end
17
+ end
18
+
19
+ class SizeModel < JSONConverter
20
+ attr_reader :raw_value, :value, :unit
21
+
22
+ def initialize(raw_value, value, unit)
23
+ super()
24
+ @raw_value = raw_value
25
+ @value = value
26
+ @unit = unit
27
+ end
28
+
29
+ def self.placeholder
30
+ SizeModel.new('Unknown', 0, MemorySize::UNIT[:bytes])
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helper/json_converter'
4
+ class DeviceModel < JSONConverter
5
+ attr_reader :device, :os_version
6
+
7
+ PARSING_KEYS = {
8
+ device: 'device: ',
9
+ os_version: 'os-version: '
10
+ }.freeze
11
+
12
+ def initialize(device, os_version)
13
+ super()
14
+ @device = device
15
+ @os_version = os_version
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helper/json_converter'
4
+ class VariantModel < JSONConverter
5
+ attr_reader :variant, :supported_variant_descriptors, :app_on_demand_resources_size, :app_size,
6
+ :on_demand_resources_size
7
+
8
+ PARSING_KEYS = {
9
+ variant: 'Variant: ',
10
+ supported_variant_descriptors: 'Supported variant descriptors: ',
11
+ app_on_demand_resources_size: 'App + On Demand Resources size: ',
12
+ app_size: 'App size: ',
13
+ on_demand_resources_size: 'On Demand Resources size: '
14
+ }.freeze
15
+
16
+ def initialize(variant, supported_variant_descriptors, app_on_demand_resources_size, app_size, on_demand_resources_size)
17
+ super()
18
+ @variant = variant
19
+ @supported_variant_descriptors = supported_variant_descriptors
20
+ @app_on_demand_resources_size = app_on_demand_resources_size
21
+ @app_size = app_size
22
+ @on_demand_resources_size = on_demand_resources_size
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: false
2
+
3
+ require_relative '../models/app_size_model'
4
+ require_relative '../helper/memory_size'
5
+ require_relative './model_parser'
6
+
7
+ class AppSizeParser < ModelParser
8
+ attr_reader :standardized_unit
9
+
10
+ def initialize(text, standardized_unit: MemorySize::UNIT[:megabytes])
11
+ super(text)
12
+ @standardized_unit = standardized_unit
13
+ end
14
+
15
+ def parse
16
+ @text = @text.strip
17
+ if @text.empty?
18
+ @result = nil
19
+ else
20
+ parseable_text = @text.split(', ')
21
+ properties = {}
22
+ parsing_keys = AppSizeModel::PARSING_KEYS
23
+ parseable_text.each do |size_text|
24
+ parsing_keys.each do |_property, key|
25
+ if size_text.include?(key) && !properties.fetch(key, nil)
26
+ value = size_text.gsub!(key, '')
27
+ properties[key] = value.strip
28
+ end
29
+ end
30
+ end
31
+
32
+ compressed_string = properties.fetch(parsing_keys[:compressed], nil)
33
+ uncompressed_string = properties.fetch(parsing_keys[:uncompressed], nil)
34
+ compressed_value = MemorySize.new(compressed_string).megabytes
35
+ uncompressed_value = MemorySize.new(uncompressed_string).megabytes
36
+
37
+ if compressed_string && uncompressed_string && compressed_value && uncompressed_value
38
+ compressed_raw_value = compressed_string.downcase == MemorySize::ZERO_SIZE ? '0 KB' : compressed_string
39
+ compressed_size = SizeModel.new(compressed_raw_value, compressed_value, @standardized_unit)
40
+ uncompressed_raw_value = uncompressed_string.downcase == MemorySize::ZERO_SIZE ? '0 KB' : uncompressed_string
41
+ uncompressed_size = SizeModel.new(uncompressed_raw_value, uncompressed_value, @standardized_unit)
42
+ @result = AppSizeModel.new(compressed: compressed_size, uncompressed: uncompressed_size)
43
+ else
44
+ @result = AppSizeModel.new
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: false
2
+
3
+ class ModelParser
4
+ attr_reader :text, :result
5
+
6
+ def initialize(text)
7
+ @text = text
8
+ end
9
+
10
+ def parse
11
+ raise NotImplementedError, 'Implement this method in a child class'
12
+ end
13
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: false
2
+
3
+ require 'securerandom'
4
+ require_relative '../models/variant_model'
5
+ require_relative '../helper/result_factory'
6
+
7
+ class ReportParser
8
+ def self.parse(text)
9
+ splitter_id = SecureRandom.uuid
10
+
11
+ # First we trim the report text
12
+ preprocessed_data = text.strip
13
+
14
+ preprocessed_data.gsub!(/\n{2,3}/, "\n#{splitter_id}\n")
15
+
16
+ # Also append the splitter ID to the end of the text so we do not miss the last variant
17
+ preprocessed_data += "\n#{splitter_id}\n"
18
+
19
+ data = preprocessed_data.split("\n")
20
+
21
+ variants = []
22
+ dict = {}
23
+
24
+ data.each do |value|
25
+ parsing_keys = VariantModel::PARSING_KEYS
26
+ if value == splitter_id && dict.fetch(parsing_keys[:variant], nil)
27
+ variant = dict.fetch(parsing_keys[:variant], '')
28
+ supported_variant_descriptors = dict.fetch(parsing_keys[:supported_variant_descriptors], '')
29
+ app_on_demand_resources_size = dict.fetch(parsing_keys[:app_on_demand_resources_size], '')
30
+ app_size = dict.fetch(parsing_keys[:app_size], '')
31
+ on_demand_resources_size = dict.fetch(parsing_keys[:on_demand_resources_size], '')
32
+
33
+ # initialize variant model from all the parser result
34
+ model = VariantModel.new(variant,
35
+ supported_variant_descriptors,
36
+ app_on_demand_resources_size,
37
+ app_size,
38
+ on_demand_resources_size)
39
+
40
+ variants.append(model)
41
+
42
+ # reset all the properties
43
+ dict = {}
44
+ end
45
+
46
+ parsing_keys.each do |property, key|
47
+ next unless value.include? key
48
+
49
+ # clean the key from the text
50
+ # i.e. "Variant: ChargePointAppClip-354363463-...." remove the "Variant: " so we have a clean text that we can parse ("ChargePointAppClip-354363463-....")
51
+ # i.e. "Supported variant descriptors: [device: iPhone10,3, os-version:14.0], ..." will pass only the "[device: iPhone10,3, os-version:14.0], ..." to the parser
52
+ if (key == parsing_keys[:on_demand_resources_size]) && (value.include? parsing_keys[:app_on_demand_resources_size])
53
+ parseable_text = value.gsub(parsing_keys[:app_on_demand_resources_size], '')
54
+ dict[key] = parseable_text
55
+ dict[key] = ResultFactory.parse(from_text: parseable_text, parser: :app_on_demand_resources_size)
56
+ else
57
+ parseable_text = value.gsub(key, '')
58
+ dict[key] = parseable_text
59
+ dict[key] = ResultFactory.parse(from_text: parseable_text, parser: property)
60
+ end
61
+ end
62
+ end
63
+ variants
64
+ end
65
+ end