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.
- checksums.yaml +4 -4
- data/Gemfile +3 -1
- data/Gemfile.lock +170 -0
- data/Guardfile +2 -0
- data/LICENSE +21 -0
- data/README.md +46 -8
- data/Rakefile +2 -0
- data/Resources/App Thinning Size Report.txt +57 -0
- data/Resources/expectedReportJSON.json +590 -0
- data/danger-app_size_report.gemspec +13 -11
- data/lib/app_size_report/gem_version.rb +3 -1
- data/lib/app_size_report/plugin.rb +152 -78
- data/lib/converter/helper/json_converter.rb +14 -0
- data/lib/converter/helper/memory_size.rb +107 -0
- data/lib/converter/helper/result_factory.rb +34 -0
- data/lib/converter/models/app_size_model.rb +32 -0
- data/lib/converter/models/device_model.rb +17 -0
- data/lib/converter/models/variant_model.rb +24 -0
- data/lib/converter/parser/app_size_parser.rb +48 -0
- data/lib/converter/parser/model_parser.rb +13 -0
- data/lib/converter/parser/report_parser.rb +65 -0
- data/lib/converter/parser/variant_descriptor_parser.rb +53 -0
- data/lib/converter/parser/variant_parser.rb +10 -0
- data/lib/danger_app_size_report.rb +3 -1
- data/lib/danger_plugin.rb +3 -1
- data/spec/app_size_report_spec.rb +18 -27
- data/spec/spec_helper.rb +19 -17
- metadata +35 -21
- data/LICENSE.txt +0 -22
| @@ -1,111 +1,185 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            require 'set'
         | 
| 1 | 
            +
            # frozen_string_literal: false
         | 
| 3 2 |  | 
| 4 3 | 
             
            module Danger
         | 
| 5 | 
            -
               | 
| 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 | 
            -
              #  | 
| 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 | 
            -
              # | 
| 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 | 
            -
              # | 
| 15 | 
            -
              # | 
| 16 | 
            -
              # | 
| 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 | 
            -
                #  | 
| 21 | 
            -
                # @ | 
| 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  | 
| 24 | 
            -
                   | 
| 25 | 
            -
             | 
| 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 | 
            -
             | 
| 35 | 
            -
                     | 
| 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 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 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 | 
            -
                   | 
| 55 | 
            -
                     | 
| 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 | 
            -
                   | 
| 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 | 
            -
                #  | 
| 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  | 
| 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 = #{ | 
| 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 << "|  | 
| 69 | 
            -
             | 
| 70 | 
            -
                   | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
                     | 
| 74 | 
            -
                     | 
| 75 | 
            -
                     | 
| 76 | 
            -
                    
         | 
| 77 | 
            -
                     | 
| 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 | 
            -
                 | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
                     | 
| 90 | 
            -
                     | 
| 91 | 
            -
             | 
| 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 | 
            -
                     | 
| 174 | 
            +
                    variant_descriptors_report << "</details> \n\n"
         | 
| 97 175 | 
             
                  end
         | 
| 98 176 |  | 
| 99 | 
            -
                  markdown  | 
| 177 | 
            +
                  markdown variant_descriptors_report
         | 
| 100 178 | 
             
                end
         | 
| 101 179 |  | 
| 102 | 
            -
                 | 
| 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,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
         |