github_repo_statistics 2.0.8
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/.github/CODEOWNERS +2 -0
- data/.github/workflows/hf_orc-gating.yaml +24 -0
- data/.github/workflows/hf_validate-repository-ownership.yaml +20 -0
- data/.github/workflows/hf_validate-tribe-squad-labels.yaml +32 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +69 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +12 -0
- data/bin/console +11 -0
- data/bin/github_repo_statistics +131 -0
- data/bin/setup +8 -0
- data/github_repo_statistics.gemspec +29 -0
- data/lib/github_repo_statistics/github_repo_statistics.rb +290 -0
- data/lib/github_repo_statistics/version.rb +5 -0
- data/lib/github_repo_statistics.rb +8 -0
- data/sig/github_repo_statistics.rbs +4 -0
- data/spec/fixtures/file.wrong +0 -0
- data/spec/fixtures/file1.swift +333 -0
- data/spec/fixtures/file2.kt +243 -0
- data/spec/fixtures/file3.swift +330 -0
- data/spec/fixtures/file4.swift +235 -0
- data/spec/fixtures/ignore.kt +0 -0
- data/spec/github_repo_statistics.rb +92 -0
- data/spec/spec_helper.rb +15 -0
- metadata +112 -0
| @@ -0,0 +1,290 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'pry'
         | 
| 4 | 
            +
            require 'date'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class GithubRepoStatistics
         | 
| 7 | 
            +
              def initialize(directory_path:, duration_in_days:, begin_time:, debug: nil, steps: 1)
         | 
| 8 | 
            +
                @directory_path = directory_path
         | 
| 9 | 
            +
                @duration_in_days = duration_in_days
         | 
| 10 | 
            +
                @begin_time = begin_time
         | 
| 11 | 
            +
                @debug = debug
         | 
| 12 | 
            +
                @steps = steps
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def true?(obj)
         | 
| 16 | 
            +
                obj.to_s.downcase == 'true'
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def read_codeowners_file
         | 
| 20 | 
            +
                raise "CODEOWNERS file does not exist under #{CODEOWNERS_PATH}" unless File.exist?(CODEOWNERS_PATH)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                codeowners = {}
         | 
| 23 | 
            +
                File.readlines(CODEOWNERS_PATH).each do |line|
         | 
| 24 | 
            +
                  next if line.strip.empty? || line.start_with?('#') # Skip comments and empty lines
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  parts = line.split(/\s+/)
         | 
| 27 | 
            +
                  directory_pattern = parts[0]
         | 
| 28 | 
            +
                  owner = parts[1..].map { |o| o.start_with?('@') ? o[1..] : o }.join(' ') # Remove leading '@' from team names
         | 
| 29 | 
            +
                  codeowners[directory_pattern] = owner
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
                codeowners
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def find_owners(file_path, codeowners)
         | 
| 35 | 
            +
                matching_patterns = codeowners.keys.select do |pattern|
         | 
| 36 | 
            +
                  pattern_regex = Regexp.new("^#{Regexp.escape(pattern.sub(%r{^/+}, '').chomp('/')).gsub('\*', '.*').gsub('**',
         | 
| 37 | 
            +
                                                                                                                          '.*?')}")
         | 
| 38 | 
            +
                  file_path =~ pattern_regex
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                return ['unknown'] if matching_patterns.empty?
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                # Sort patterns by length in descending order
         | 
| 44 | 
            +
                sorted_patterns = matching_patterns.sort_by(&:length).reverse
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # Find the most specific matching pattern
         | 
| 47 | 
            +
                best_match = sorted_patterns.find do |pattern|
         | 
| 48 | 
            +
                  pattern_regex = Regexp.new("^#{Regexp.escape(pattern.sub(%r{^/+}, '').chomp('/')).gsub('\*', '.*').gsub('**',
         | 
| 49 | 
            +
                                                                                                                          '.*?')}")
         | 
| 50 | 
            +
                  file_path =~ pattern_regex
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                codeowners[best_match].split(' ')
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              def handle_codeowners(file_team_map:)
         | 
| 57 | 
            +
                output = "\n   *Code ownership data:*\n"
         | 
| 58 | 
            +
                codeowners = read_codeowners_file
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                owners_data = Hash.new do |hash, key|
         | 
| 61 | 
            +
                  hash[key] = { directories: Hash.new do |h, k|
         | 
| 62 | 
            +
                    h[k] = { files: [] }
         | 
| 63 | 
            +
                  end, churn_count: 0 }
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                file_team_map.each do |file, count|
         | 
| 67 | 
            +
                  owners = find_owners(file, codeowners)
         | 
| 68 | 
            +
                  owners.each do |owner|
         | 
| 69 | 
            +
                    owners_data[owner][:churn_count] += count.last
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    dir_path = File.dirname(file)
         | 
| 72 | 
            +
                    owners_data[owner][:directories][dir_path][:files] << { name: File.basename(file), count: }
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                # Sort owners_data by total count in descending order
         | 
| 77 | 
            +
                sorted_owners_data = owners_data.sort_by { |_, data| -data[:churn_count] }
         | 
| 78 | 
            +
                converted_team_map = file_team_map.transform_keys { |key| File.basename(key) }
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                sorted_owners_data.each do |owner, data|
         | 
| 81 | 
            +
                  output += "\n      #{owner.split('/').last}:\n         Total Count: #{data[:churn_count]}\n"
         | 
| 82 | 
            +
                  data[:directories].each do |dir, dir_data|
         | 
| 83 | 
            +
                    output += "            Directory: #{dir}\n               Top files:\n"
         | 
| 84 | 
            +
                    dir_data[:files].each do |file_data|
         | 
| 85 | 
            +
                      next if converted_team_map[File.basename(file_data[:name])].nil?
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      contributors = converted_team_map[file_data[:name]]&.first&.empty? ? ['Excluded contributor'] : converted_team_map[file_data[:name]].first
         | 
| 88 | 
            +
                      output += "                  #{File.basename(file_data[:name])} - #{file_data[:count].last} #{contributors}}\n"
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                if FILE_OUTPUT
         | 
| 94 | 
            +
                  File.open('codeowners.txt', 'w') do |f|
         | 
| 95 | 
            +
                    f.puts output
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
                else
         | 
| 98 | 
            +
                  puts output
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
              def find_owner(file:)
         | 
| 103 | 
            +
                codeowners = read_codeowners_file
         | 
| 104 | 
            +
                find_owners(file, codeowners)
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
              def count_big_files(directory_path, size: BIG_FILE_SIZE)
         | 
| 108 | 
            +
                size = size.to_i
         | 
| 109 | 
            +
                # Get a list of all files in the specified directory
         | 
| 110 | 
            +
                files = Dir.glob(File.join(directory_path, '**', '*')).select { |file| File.file?(file) }
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                code_files = files.select do |f|
         | 
| 113 | 
            +
                  extension = File.extname(f)
         | 
| 114 | 
            +
                  valid_extensions = CODE_EXTENSIONS
         | 
| 115 | 
            +
                  valid_extensions.include?(extension)
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                # Initialize a counter for files that meet the criteria
         | 
| 119 | 
            +
                count = 0
         | 
| 120 | 
            +
                # Iterate through each file and check the line count
         | 
| 121 | 
            +
                code_files.each do |file|
         | 
| 122 | 
            +
                  lines_count = File.foreach(file).reject { |line| line.match(%r{^\s*(//|/\*.*\*/|\s*$)}) }.count
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  count += 1 if lines_count > size
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                puts "     *Current total number of code files longer than #{size} lines:* #{count}"
         | 
| 128 | 
            +
              end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
              def count_hotspot_lines(files)
         | 
| 131 | 
            +
                code_files = files.select do |f|
         | 
| 132 | 
            +
                  extension = File.extname(f)
         | 
| 133 | 
            +
                  valid_extensions = CODE_EXTENSIONS
         | 
| 134 | 
            +
                  valid_extensions.include?(extension)
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                count = 0
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                code_files.each do |file|
         | 
| 140 | 
            +
                  lines_count = File.foreach(file).reject { |line| line.match(%r{^\s*(//|/\*.*\*/|\s*$)}) }.count
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  count += lines_count
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                puts "     *Total lines of hotspot code:* #{count}"
         | 
| 146 | 
            +
              end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
              def filter_existing_code_files(files)
         | 
| 149 | 
            +
                files.select do |f|
         | 
| 150 | 
            +
                  next unless File.exist?(f)
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  if EXCLUDED_FILES
         | 
| 153 | 
            +
                    excluded_patterns = EXCLUDED_FILES.split(',')
         | 
| 154 | 
            +
                    next if excluded_patterns.any? { |pattern| f.include?(pattern) }
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  extension = File.extname(f)
         | 
| 158 | 
            +
                  valid_extensions = CODE_EXTENSIONS
         | 
| 159 | 
            +
                  valid_extensions.include?(extension)
         | 
| 160 | 
            +
                end
         | 
| 161 | 
            +
              end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
              def git_files(directory_path:)
         | 
| 164 | 
            +
                `git ls-tree -r --name-only $(git rev-list -1 HEAD) -- "#{directory_path}"`
         | 
| 165 | 
            +
              end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
              def files_with_changes(directory_path:, start_date:, end_date:)
         | 
| 168 | 
            +
                `git log --name-only --pretty=format:"" --since="#{start_date}" --until="#{end_date}" "#{directory_path}"`
         | 
| 169 | 
            +
              end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
              def git_commit_count(file:, start_date:, end_date:)
         | 
| 172 | 
            +
                `git log --since="#{start_date}" --until="#{end_date}" --follow -- "#{file}" | grep -c '^commit'`
         | 
| 173 | 
            +
              end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
              def git_commit_info(file:, start_date:, end_date:)
         | 
| 176 | 
            +
                `git log --pretty=format:"%s" --since="#{start_date}" --until="#{end_date}" --follow -- "#{file}"`
         | 
| 177 | 
            +
              end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
              def analyze_changed_files(uniq_code_files_with_changes:, start_date:, end_date:)
         | 
| 180 | 
            +
                all_teams = []
         | 
| 181 | 
            +
                cross_teams_count = 0
         | 
| 182 | 
            +
                single_ownership_teams_count = 0
         | 
| 183 | 
            +
                files_changed_by_many_teams = 0
         | 
| 184 | 
            +
                total_changes = 0
         | 
| 185 | 
            +
                file_team_map = {}
         | 
| 186 | 
            +
                uniq_code_files_with_changes.each do |file|
         | 
| 187 | 
            +
                  filename = File.basename(file)
         | 
| 188 | 
            +
                  commit_count = git_commit_count(file:, start_date:, end_date:).to_i
         | 
| 189 | 
            +
                  git_log = git_commit_info(file:, start_date:, end_date:).split("\n")
         | 
| 190 | 
            +
                  teams = git_log.map do |team|
         | 
| 191 | 
            +
                    team.match(/#{TEAM_REGEX}/)[0].upcase
         | 
| 192 | 
            +
                  end.reject { |e| EXCLUSIONS&.include?(e) }
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                  total_changes += commit_count
         | 
| 195 | 
            +
                  all_teams << teams
         | 
| 196 | 
            +
                  teams = teams.uniq
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                  if teams.count > 1
         | 
| 199 | 
            +
                    files_changed_by_many_teams += 1
         | 
| 200 | 
            +
                    file_team_map.merge!(file.to_s => [teams, commit_count])
         | 
| 201 | 
            +
                    cross_teams_count += teams.count
         | 
| 202 | 
            +
                  else
         | 
| 203 | 
            +
                    single_ownership_teams_count += 1
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                  puts "\n#{filename} [#{commit_count}]:#{teams}\n" if @debug
         | 
| 207 | 
            +
                end
         | 
| 208 | 
            +
                [all_teams, cross_teams_count, single_ownership_teams_count, files_changed_by_many_teams, total_changes, file_team_map]
         | 
| 209 | 
            +
              end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
              def filter_files(file_team_map:)
         | 
| 212 | 
            +
                file_team_map.select do |file_path|
         | 
| 213 | 
            +
                  next unless File.exist?(file_path)
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  # Check if the file size is more than BIG_FILE_SIZE lines (excluding empty and commented lines)
         | 
| 216 | 
            +
                  File.foreach(file_path).reject { |line| line.match(%r{^\s*(//|/\*.*\*/|\s*$)}) }.count > BIG_FILE_SIZE.to_i
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
              end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
              def contribution_message
         | 
| 221 | 
            +
                duration_in_days = @duration_in_days.to_i
         | 
| 222 | 
            +
                start_date = @begin_time.to_time.to_i - duration_in_days * 86_400 - 30 * 86_400
         | 
| 223 | 
            +
                end_date = @begin_time.to_time.to_i - 30 * 86_400
         | 
| 224 | 
            +
                git_ls = git_files(directory_path: @directory_path)
         | 
| 225 | 
            +
                file_count = filter_existing_code_files(git_ls.split).count
         | 
| 226 | 
            +
                all_files_with_changes = files_with_changes(directory_path: @directory_path, start_date:, end_date:).split.sort
         | 
| 227 | 
            +
                code_files_with_changes = filter_existing_code_files(all_files_with_changes)
         | 
| 228 | 
            +
                uniq_code_files_with_changes = code_files_with_changes.uniq
         | 
| 229 | 
            +
                all_teams, cross_teams_count, single_ownership_teams_count, files_changed_by_many_teams, total_changes, file_team_map = analyze_changed_files(uniq_code_files_with_changes:, start_date:, end_date:)
         | 
| 230 | 
            +
                occurrences = all_teams.flatten.compact.tally
         | 
| 231 | 
            +
                sorted_occurrences = occurrences.sort_by { |element, count| [-count, element] }
         | 
| 232 | 
            +
                contributors = Hash[sorted_occurrences]
         | 
| 233 | 
            +
                churn_count = file_team_map.values.map { |value| value[1] }.sum
         | 
| 234 | 
            +
                hotspot_changes_percentage = (churn_count.to_f / total_changes) * 100
         | 
| 235 | 
            +
                # Filter files based on extension, existence and size
         | 
| 236 | 
            +
                filtered_files = filter_files(file_team_map:)
         | 
| 237 | 
            +
                filtered_top_touched_files = filtered_files.sort_by { |element, count| [-count.last, element] }
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                puts ''
         | 
| 240 | 
            +
                puts "*Timeframe:* #{(@begin_time - duration_in_days).strftime('%Y-%m-%d')} to #{@begin_time.strftime('%Y-%m-%d')}"
         | 
| 241 | 
            +
                puts "     *Code files with a single contributor:* #{(100 - ((files_changed_by_many_teams.to_f / file_count) * 100)).round(2)}%"
         | 
| 242 | 
            +
                puts "        *Existing files changed by many teams:* #{files_changed_by_many_teams}"
         | 
| 243 | 
            +
                puts "        *Current existing #{CODE_EXTENSIONS} files:* #{file_count}"
         | 
| 244 | 
            +
                puts '     *Cross-Squad Dependency:*'
         | 
| 245 | 
            +
                puts "        *Contributions by multiple squads to the same files:* #{cross_teams_count}"
         | 
| 246 | 
            +
                puts "        *Contributions by single squads contributing to single files:* #{single_ownership_teams_count}"
         | 
| 247 | 
            +
                puts "     *Hotspot Code Changes:* #{hotspot_changes_percentage.round(2)}%"
         | 
| 248 | 
            +
                puts "        *Churn count(commits to files by multiple teams):* #{churn_count}"
         | 
| 249 | 
            +
                puts "        *Total amount of commits:* #{total_changes}"
         | 
| 250 | 
            +
                count_hotspot_lines(filtered_files.keys)
         | 
| 251 | 
            +
                puts "     *#{CODE_EXTENSIONS} files with multiple contributors:* #{file_team_map.count}"
         | 
| 252 | 
            +
                puts "     *#{CODE_EXTENSIONS} files exceeding #{BIG_FILE_SIZE} lines with multiple contributors:* #{filtered_top_touched_files.count}"
         | 
| 253 | 
            +
                puts "     *Total amount of commits to #{CODE_EXTENSIONS} files:* #{total_changes}"
         | 
| 254 | 
            +
                puts "     *Total #{CODE_EXTENSIONS} files changed:* #{uniq_code_files_with_changes.count}"
         | 
| 255 | 
            +
                count_big_files(@directory_path)
         | 
| 256 | 
            +
                puts "     *Current total of #{CODE_EXTENSIONS} files in the folder:* #{file_count}"
         | 
| 257 | 
            +
                puts "     *Contributors:* #{contributors}"
         | 
| 258 | 
            +
             | 
| 259 | 
            +
                if HOTSPOT
         | 
| 260 | 
            +
                  hotspot_output = "\n   *Hotspot files(#{filtered_top_touched_files.count}):*\n"
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                  filtered_top_touched_files.each do |line|
         | 
| 263 | 
            +
                    hotspot_output += "\n"
         | 
| 264 | 
            +
                    file = line.first
         | 
| 265 | 
            +
                    contributors = line.last.first
         | 
| 266 | 
            +
                    commits = line.last.last
         | 
| 267 | 
            +
                    hotspot_output += "     #{file.gsub(@directory_path, '')} Contributors: #{contributors} Commits: #{commits} Owner: #{find_owner(file:)}\n"
         | 
| 268 | 
            +
                  end
         | 
| 269 | 
            +
             | 
| 270 | 
            +
                  if FILE_OUTPUT
         | 
| 271 | 
            +
                    File.open('hotspot.txt', 'w') do |f|
         | 
| 272 | 
            +
                      f.puts hotspot_output
         | 
| 273 | 
            +
                    end
         | 
| 274 | 
            +
                  else
         | 
| 275 | 
            +
                    puts hotspot_output
         | 
| 276 | 
            +
                  end
         | 
| 277 | 
            +
                end
         | 
| 278 | 
            +
             | 
| 279 | 
            +
                handle_codeowners(file_team_map:) if CODEOWNERS
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                @steps -= 1
         | 
| 282 | 
            +
             | 
| 283 | 
            +
                return unless @steps.positive?
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                system("git checkout `git rev-list -1 --before='#{(@begin_time - duration_in_days).strftime('%B %d %Y')}' HEAD`",
         | 
| 286 | 
            +
                       %i[out err] => File::NULL)
         | 
| 287 | 
            +
                @begin_time -= duration_in_days
         | 
| 288 | 
            +
                contribution_message
         | 
| 289 | 
            +
              end
         | 
| 290 | 
            +
            end
         | 
| 
            File without changes
         | 
| @@ -0,0 +1,333 @@ | |
| 1 | 
            +
            line
         | 
| 2 | 
            +
            line
         | 
| 3 | 
            +
            line
         | 
| 4 | 
            +
            line
         | 
| 5 | 
            +
            line
         | 
| 6 | 
            +
            line
         | 
| 7 | 
            +
            line
         | 
| 8 | 
            +
            line
         | 
| 9 | 
            +
            line
         | 
| 10 | 
            +
            line
         | 
| 11 | 
            +
            line
         | 
| 12 | 
            +
            line
         | 
| 13 | 
            +
            line
         | 
| 14 | 
            +
            line
         | 
| 15 | 
            +
            line
         | 
| 16 | 
            +
            line
         | 
| 17 | 
            +
            line
         | 
| 18 | 
            +
            line
         | 
| 19 | 
            +
            line
         | 
| 20 | 
            +
            line
         | 
| 21 | 
            +
            line
         | 
| 22 | 
            +
            line
         | 
| 23 | 
            +
            line
         | 
| 24 | 
            +
            line
         | 
| 25 | 
            +
            line
         | 
| 26 | 
            +
            line
         | 
| 27 | 
            +
            line
         | 
| 28 | 
            +
            line
         | 
| 29 | 
            +
            line
         | 
| 30 | 
            +
            line
         | 
| 31 | 
            +
            line
         | 
| 32 | 
            +
            line
         | 
| 33 | 
            +
            line
         | 
| 34 | 
            +
            line
         | 
| 35 | 
            +
            line
         | 
| 36 | 
            +
            line
         | 
| 37 | 
            +
            line
         | 
| 38 | 
            +
            line
         | 
| 39 | 
            +
            line
         | 
| 40 | 
            +
            line
         | 
| 41 | 
            +
            line
         | 
| 42 | 
            +
            line
         | 
| 43 | 
            +
            line
         | 
| 44 | 
            +
            line
         | 
| 45 | 
            +
            line
         | 
| 46 | 
            +
            line
         | 
| 47 | 
            +
            line
         | 
| 48 | 
            +
            line
         | 
| 49 | 
            +
            line
         | 
| 50 | 
            +
            line
         | 
| 51 | 
            +
            line
         | 
| 52 | 
            +
            line
         | 
| 53 | 
            +
            line
         | 
| 54 | 
            +
            line
         | 
| 55 | 
            +
            line
         | 
| 56 | 
            +
            line
         | 
| 57 | 
            +
            line
         | 
| 58 | 
            +
            line
         | 
| 59 | 
            +
            line
         | 
| 60 | 
            +
            line
         | 
| 61 | 
            +
            line
         | 
| 62 | 
            +
            line
         | 
| 63 | 
            +
            line
         | 
| 64 | 
            +
            line
         | 
| 65 | 
            +
            line
         | 
| 66 | 
            +
            line
         | 
| 67 | 
            +
            line
         | 
| 68 | 
            +
            line
         | 
| 69 | 
            +
            line
         | 
| 70 | 
            +
            line
         | 
| 71 | 
            +
            line
         | 
| 72 | 
            +
            line
         | 
| 73 | 
            +
            line
         | 
| 74 | 
            +
            line
         | 
| 75 | 
            +
            line
         | 
| 76 | 
            +
            line
         | 
| 77 | 
            +
            line
         | 
| 78 | 
            +
            line
         | 
| 79 | 
            +
            line
         | 
| 80 | 
            +
            line
         | 
| 81 | 
            +
            line
         | 
| 82 | 
            +
            line
         | 
| 83 | 
            +
            line
         | 
| 84 | 
            +
            line
         | 
| 85 | 
            +
            line
         | 
| 86 | 
            +
            line
         | 
| 87 | 
            +
            line
         | 
| 88 | 
            +
            line
         | 
| 89 | 
            +
            line
         | 
| 90 | 
            +
            line
         | 
| 91 | 
            +
            line
         | 
| 92 | 
            +
            line
         | 
| 93 | 
            +
            line
         | 
| 94 | 
            +
            line
         | 
| 95 | 
            +
            line
         | 
| 96 | 
            +
            line
         | 
| 97 | 
            +
            line
         | 
| 98 | 
            +
            line
         | 
| 99 | 
            +
            line
         | 
| 100 | 
            +
            line
         | 
| 101 | 
            +
            line
         | 
| 102 | 
            +
            line
         | 
| 103 | 
            +
            line
         | 
| 104 | 
            +
            line
         | 
| 105 | 
            +
            line
         | 
| 106 | 
            +
            line
         | 
| 107 | 
            +
            line
         | 
| 108 | 
            +
            line
         | 
| 109 | 
            +
            line
         | 
| 110 | 
            +
            line
         | 
| 111 | 
            +
            line
         | 
| 112 | 
            +
            line
         | 
| 113 | 
            +
            line
         | 
| 114 | 
            +
            line
         | 
| 115 | 
            +
            line
         | 
| 116 | 
            +
            line
         | 
| 117 | 
            +
            line
         | 
| 118 | 
            +
            line
         | 
| 119 | 
            +
            line
         | 
| 120 | 
            +
            line
         | 
| 121 | 
            +
            line
         | 
| 122 | 
            +
            line
         | 
| 123 | 
            +
            line
         | 
| 124 | 
            +
            line
         | 
| 125 | 
            +
            line
         | 
| 126 | 
            +
            line
         | 
| 127 | 
            +
            line
         | 
| 128 | 
            +
            line
         | 
| 129 | 
            +
            line
         | 
| 130 | 
            +
            line
         | 
| 131 | 
            +
            line
         | 
| 132 | 
            +
            line
         | 
| 133 | 
            +
            line
         | 
| 134 | 
            +
            line
         | 
| 135 | 
            +
            line
         | 
| 136 | 
            +
            line
         | 
| 137 | 
            +
            line
         | 
| 138 | 
            +
            line
         | 
| 139 | 
            +
            line
         | 
| 140 | 
            +
            line
         | 
| 141 | 
            +
            line
         | 
| 142 | 
            +
            line
         | 
| 143 | 
            +
            line
         | 
| 144 | 
            +
            line
         | 
| 145 | 
            +
            line
         | 
| 146 | 
            +
            line
         | 
| 147 | 
            +
            line
         | 
| 148 | 
            +
            line
         | 
| 149 | 
            +
            line
         | 
| 150 | 
            +
            line
         | 
| 151 | 
            +
            line
         | 
| 152 | 
            +
            line
         | 
| 153 | 
            +
            line
         | 
| 154 | 
            +
            line
         | 
| 155 | 
            +
            line
         | 
| 156 | 
            +
            line
         | 
| 157 | 
            +
            line
         | 
| 158 | 
            +
            line
         | 
| 159 | 
            +
            line
         | 
| 160 | 
            +
            line
         | 
| 161 | 
            +
            line
         | 
| 162 | 
            +
            line
         | 
| 163 | 
            +
            line
         | 
| 164 | 
            +
            line
         | 
| 165 | 
            +
            line
         | 
| 166 | 
            +
            line
         | 
| 167 | 
            +
            line
         | 
| 168 | 
            +
            line
         | 
| 169 | 
            +
            line
         | 
| 170 | 
            +
            line
         | 
| 171 | 
            +
            line
         | 
| 172 | 
            +
            line
         | 
| 173 | 
            +
            line
         | 
| 174 | 
            +
            line
         | 
| 175 | 
            +
            line
         | 
| 176 | 
            +
            line
         | 
| 177 | 
            +
            line
         | 
| 178 | 
            +
            line
         | 
| 179 | 
            +
            line
         | 
| 180 | 
            +
            line
         | 
| 181 | 
            +
            line
         | 
| 182 | 
            +
            line
         | 
| 183 | 
            +
            line
         | 
| 184 | 
            +
            line
         | 
| 185 | 
            +
            line
         | 
| 186 | 
            +
            line
         | 
| 187 | 
            +
            line
         | 
| 188 | 
            +
            line
         | 
| 189 | 
            +
            line
         | 
| 190 | 
            +
            line
         | 
| 191 | 
            +
            line
         | 
| 192 | 
            +
            line
         | 
| 193 | 
            +
            line
         | 
| 194 | 
            +
            line
         | 
| 195 | 
            +
            line
         | 
| 196 | 
            +
            line
         | 
| 197 | 
            +
            line
         | 
| 198 | 
            +
            line
         | 
| 199 | 
            +
            line
         | 
| 200 | 
            +
            line
         | 
| 201 | 
            +
            line
         | 
| 202 | 
            +
            line
         | 
| 203 | 
            +
            line
         | 
| 204 | 
            +
            line
         | 
| 205 | 
            +
            line
         | 
| 206 | 
            +
            line
         | 
| 207 | 
            +
            line
         | 
| 208 | 
            +
            line
         | 
| 209 | 
            +
            line
         | 
| 210 | 
            +
            line
         | 
| 211 | 
            +
            line
         | 
| 212 | 
            +
            line
         | 
| 213 | 
            +
            line
         | 
| 214 | 
            +
            line
         | 
| 215 | 
            +
            line
         | 
| 216 | 
            +
            line
         | 
| 217 | 
            +
            line
         | 
| 218 | 
            +
            line
         | 
| 219 | 
            +
            line
         | 
| 220 | 
            +
            line
         | 
| 221 | 
            +
            line
         | 
| 222 | 
            +
            line
         | 
| 223 | 
            +
            line
         | 
| 224 | 
            +
            line
         | 
| 225 | 
            +
            line
         | 
| 226 | 
            +
            line
         | 
| 227 | 
            +
            line
         | 
| 228 | 
            +
            line
         | 
| 229 | 
            +
            line
         | 
| 230 | 
            +
            line
         | 
| 231 | 
            +
            line
         | 
| 232 | 
            +
            line
         | 
| 233 | 
            +
            line
         | 
| 234 | 
            +
            line
         | 
| 235 | 
            +
            line
         | 
| 236 | 
            +
            line
         | 
| 237 | 
            +
            line
         | 
| 238 | 
            +
            line
         | 
| 239 | 
            +
            line
         | 
| 240 | 
            +
            line
         | 
| 241 | 
            +
            line
         | 
| 242 | 
            +
            line
         | 
| 243 | 
            +
            line
         | 
| 244 | 
            +
            line
         | 
| 245 | 
            +
            line
         | 
| 246 | 
            +
            line
         | 
| 247 | 
            +
            line
         | 
| 248 | 
            +
            line
         | 
| 249 | 
            +
            line
         | 
| 250 | 
            +
            line
         | 
| 251 | 
            +
            line
         | 
| 252 | 
            +
            line
         | 
| 253 | 
            +
            line
         | 
| 254 | 
            +
            line
         | 
| 255 | 
            +
            line
         | 
| 256 | 
            +
            line
         | 
| 257 | 
            +
            line
         | 
| 258 | 
            +
            line
         | 
| 259 | 
            +
            line
         | 
| 260 | 
            +
            line
         | 
| 261 | 
            +
            line
         | 
| 262 | 
            +
            line
         | 
| 263 | 
            +
            line
         | 
| 264 | 
            +
            line
         | 
| 265 | 
            +
            line
         | 
| 266 | 
            +
            line
         | 
| 267 | 
            +
            line
         | 
| 268 | 
            +
            line
         | 
| 269 | 
            +
            line
         | 
| 270 | 
            +
            line
         | 
| 271 | 
            +
            line
         | 
| 272 | 
            +
            line
         | 
| 273 | 
            +
            line
         | 
| 274 | 
            +
            line
         | 
| 275 | 
            +
            line
         | 
| 276 | 
            +
            line
         | 
| 277 | 
            +
            line
         | 
| 278 | 
            +
            line
         | 
| 279 | 
            +
            line
         | 
| 280 | 
            +
            line
         | 
| 281 | 
            +
            line
         | 
| 282 | 
            +
            line
         | 
| 283 | 
            +
            line
         | 
| 284 | 
            +
            line
         | 
| 285 | 
            +
            line
         | 
| 286 | 
            +
            line
         | 
| 287 | 
            +
            line
         | 
| 288 | 
            +
            line
         | 
| 289 | 
            +
            line
         | 
| 290 | 
            +
            line
         | 
| 291 | 
            +
            line
         | 
| 292 | 
            +
            line
         | 
| 293 | 
            +
            line
         | 
| 294 | 
            +
            line
         | 
| 295 | 
            +
            line
         | 
| 296 | 
            +
            line
         | 
| 297 | 
            +
            line
         | 
| 298 | 
            +
            line
         | 
| 299 | 
            +
            line
         | 
| 300 | 
            +
            line
         | 
| 301 | 
            +
            line
         | 
| 302 | 
            +
            line
         | 
| 303 | 
            +
            line
         | 
| 304 | 
            +
            line
         | 
| 305 | 
            +
            line
         | 
| 306 | 
            +
            line
         | 
| 307 | 
            +
            line
         | 
| 308 | 
            +
            line
         | 
| 309 | 
            +
            line
         | 
| 310 | 
            +
            line
         | 
| 311 | 
            +
            line
         | 
| 312 | 
            +
            line
         | 
| 313 | 
            +
            line
         | 
| 314 | 
            +
            line
         | 
| 315 | 
            +
            line
         | 
| 316 | 
            +
            line
         | 
| 317 | 
            +
            line
         | 
| 318 | 
            +
            line
         | 
| 319 | 
            +
            line
         | 
| 320 | 
            +
            line
         | 
| 321 | 
            +
            line
         | 
| 322 | 
            +
            // comment
         | 
| 323 | 
            +
             | 
| 324 | 
            +
             | 
| 325 | 
            +
            line
         | 
| 326 | 
            +
            line
         | 
| 327 | 
            +
            line
         | 
| 328 | 
            +
            line
         | 
| 329 | 
            +
            line
         | 
| 330 | 
            +
            line
         | 
| 331 | 
            +
            line
         | 
| 332 | 
            +
            line
         | 
| 333 | 
            +
            line
         |