repo_analyzer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +103 -0
  3. data/.circleci/setup-rubygems.sh +3 -0
  4. data/.editorconfig +24 -0
  5. data/.gitignore +9 -0
  6. data/.rspec +3 -0
  7. data/.rubocop.yml +505 -0
  8. data/.ruby-version +1 -0
  9. data/CHANGELOG.md +7 -0
  10. data/Gemfile +12 -0
  11. data/Gemfile.lock +320 -0
  12. data/Guardfile +12 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +165 -0
  15. data/Rakefile +10 -0
  16. data/app/assets/config/repo_analyzer_manifest.js +1 -0
  17. data/app/assets/images/repo_analyzer/.keep +0 -0
  18. data/app/assets/stylesheets/repo_analyzer/application.css +15 -0
  19. data/app/clients/repo_analyzer/github_client.rb +31 -0
  20. data/app/controllers/repo_analyzer/application_controller.rb +4 -0
  21. data/app/extractors/repo_analyzer/brakeman_extractor.rb +33 -0
  22. data/app/extractors/repo_analyzer/circleci_extractor.rb +46 -0
  23. data/app/extractors/repo_analyzer/github_extractor.rb +64 -0
  24. data/app/extractors/repo_analyzer/power_types_extractor.rb +21 -0
  25. data/app/extractors/repo_analyzer/project_config_extractor.rb +53 -0
  26. data/app/extractors/repo_analyzer/project_info_extractor.rb +20 -0
  27. data/app/extractors/repo_analyzer/project_versions_extractor.rb +80 -0
  28. data/app/extractors/repo_analyzer/rails_best_practices_extractor.rb +36 -0
  29. data/app/extractors/repo_analyzer/rubocop_extractor.rb +74 -0
  30. data/app/extractors/repo_analyzer/tests_info_extractor.rb +87 -0
  31. data/app/helpers/repo_analyzer/application_helper.rb +4 -0
  32. data/app/jobs/repo_analyzer/application_job.rb +4 -0
  33. data/app/jobs/repo_analyzer/extract_project_info_job.rb +35 -0
  34. data/app/jobs/repo_analyzer/post_extracted_info_job.rb +28 -0
  35. data/app/mailers/repo_analyzer/application_mailer.rb +6 -0
  36. data/app/models/repo_analyzer/application_record.rb +5 -0
  37. data/app/utils/output_utils.rb +10 -0
  38. data/app/values/repo_analyzer/project_data_bridge.rb +48 -0
  39. data/app/views/layouts/repo_analyzer/application.html.erb +15 -0
  40. data/bin/rails +25 -0
  41. data/config/routes.rb +2 -0
  42. data/lib/generators/repo_analyzer/install/USAGE +5 -0
  43. data/lib/generators/repo_analyzer/install/install_generator.rb +19 -0
  44. data/lib/generators/repo_analyzer/install/templates/initializer.rb +2 -0
  45. data/lib/repo_analyzer/engine.rb +15 -0
  46. data/lib/repo_analyzer/example_class.rb +7 -0
  47. data/lib/repo_analyzer/version.rb +3 -0
  48. data/lib/repo_analyzer.rb +48 -0
  49. data/lib/tasks/repo_analyzer_tasks.rake +7 -0
  50. data/repo_analyzer.gemspec +42 -0
  51. data/spec/assets/test_project/README.md +1 -0
  52. data/spec/assets/test_project/app/commands/some_comand.rb +0 -0
  53. data/spec/assets/test_project/app/javascript/src/component.spec.js +0 -0
  54. data/spec/assets/test_project/app/javascript/src/components/component.spec.js +0 -0
  55. data/spec/assets/test_project/app/observers/some_observer.rb +0 -0
  56. data/spec/assets/test_project/old_circleci_config.yml +21 -0
  57. data/spec/assets/test_project/spec/commands/command1_spec.rb +0 -0
  58. data/spec/assets/test_project/spec/commands/command2_spec.rb +0 -0
  59. data/spec/assets/test_project/spec/integration/integration1_spec.rb +0 -0
  60. data/spec/assets/test_project/spec/system/system_test_spec.rb +0 -0
  61. data/spec/assets/test_project/valid_circleci_config.yml +152 -0
  62. data/spec/assets/test_project/valid_gem_gemfile_lock +14 -0
  63. data/spec/assets/test_project/valid_gemfile_lock +684 -0
  64. data/spec/assets/test_project/valid_makefile +35 -0
  65. data/spec/assets/test_project/valid_package_json +84 -0
  66. data/spec/assets/test_project/valid_package_json_without_vue +74 -0
  67. data/spec/assets/test_project/valid_rubocop_rules.yml +54 -0
  68. data/spec/dummy/Rakefile +6 -0
  69. data/spec/dummy/app/assets/config/manifest.js +3 -0
  70. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  71. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  72. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  73. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  74. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  75. data/spec/dummy/app/javascript/packs/application.js +15 -0
  76. data/spec/dummy/app/jobs/application_job.rb +7 -0
  77. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  78. data/spec/dummy/app/models/application_record.rb +3 -0
  79. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  80. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  81. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  82. data/spec/dummy/bin/rails +4 -0
  83. data/spec/dummy/bin/rake +4 -0
  84. data/spec/dummy/bin/setup +33 -0
  85. data/spec/dummy/config/application.rb +38 -0
  86. data/spec/dummy/config/boot.rb +5 -0
  87. data/spec/dummy/config/cable.yml +10 -0
  88. data/spec/dummy/config/database.yml +25 -0
  89. data/spec/dummy/config/environment.rb +5 -0
  90. data/spec/dummy/config/environments/development.rb +81 -0
  91. data/spec/dummy/config/environments/production.rb +120 -0
  92. data/spec/dummy/config/environments/test.rb +59 -0
  93. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  94. data/spec/dummy/config/initializers/assets.rb +12 -0
  95. data/spec/dummy/config/initializers/backtrace_silencers.rb +8 -0
  96. data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
  97. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  98. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  99. data/spec/dummy/config/initializers/inflections.rb +16 -0
  100. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  101. data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
  102. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  103. data/spec/dummy/config/locales/en.yml +33 -0
  104. data/spec/dummy/config/puma.rb +43 -0
  105. data/spec/dummy/config/routes.rb +3 -0
  106. data/spec/dummy/config/storage.yml +34 -0
  107. data/spec/dummy/config.ru +6 -0
  108. data/spec/dummy/public/404.html +67 -0
  109. data/spec/dummy/public/422.html +67 -0
  110. data/spec/dummy/public/500.html +66 -0
  111. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  112. data/spec/dummy/public/apple-touch-icon.png +0 -0
  113. data/spec/dummy/public/favicon.ico +0 -0
  114. data/spec/extractors/brakeman_extractor_spec.rb +73 -0
  115. data/spec/extractors/circleci_extractor_spec.rb +45 -0
  116. data/spec/extractors/github_extractor_spec.rb +77 -0
  117. data/spec/extractors/power_types_extractor_spec.rb +18 -0
  118. data/spec/extractors/project_config_extractor_spec.rb +52 -0
  119. data/spec/extractors/project_versions_extractor_spec.rb +63 -0
  120. data/spec/extractors/rails_best_practices_extractor_spec.rb +53 -0
  121. data/spec/extractors/rubocop_extractor_spec.rb +113 -0
  122. data/spec/extractors/tests_info_extractor_spec.rb +65 -0
  123. data/spec/fixtures/files/image.png +0 -0
  124. data/spec/fixtures/files/video.mp4 +0 -0
  125. data/spec/jobs/repo_analyzer/extract_project_info_job_spec.rb +50 -0
  126. data/spec/jobs/repo_analyzer/post_extracted_info_job_spec.rb +52 -0
  127. data/spec/rails_helper.rb +42 -0
  128. data/spec/spec_helper.rb +9 -0
  129. data/spec/support/repo_analyzer_helpers.rb +37 -0
  130. metadata +518 -0
@@ -0,0 +1,64 @@
1
+ module RepoAnalyzer
2
+ class GithubExtractor < ProjectInfoExtractor
3
+ private
4
+
5
+ def extracted_info
6
+ {
7
+ last_commit_date: last_commit_date.to_s,
8
+ last_contributors: last_contributors,
9
+ contributors: contributors
10
+ }
11
+ end
12
+
13
+ def last_contributors
14
+ @last_contributors ||= contributors.select do |contributor|
15
+ contributors_from_commits.include?(contributor[:login])
16
+ end
17
+ end
18
+
19
+ def contributors_from_commits
20
+ @contributors_from_commits ||= last_commits.inject([]) do |users, commit|
21
+ if commit[:author]
22
+ login = commit[:author][:login]
23
+ users << login unless users.include?(login)
24
+ end
25
+
26
+ users
27
+ end
28
+ end
29
+
30
+ def contributors
31
+ @contributors ||= project_data_bridge.contributors.map do |contributor|
32
+ build_user_info(contributor)
33
+ end
34
+ end
35
+
36
+ def build_user_info(data)
37
+ {
38
+ login: data[:login],
39
+ avatar_url: data[:avatar_url],
40
+ contributions: data[:contributions]
41
+ }
42
+ end
43
+
44
+ def last_commits
45
+ @last_commits ||= begin
46
+ return [] if last_commit_date == :not_found
47
+
48
+ project_data_bridge.commits(
49
+ from_date: last_commit_date - 2.months,
50
+ to_date: last_commit_date + 1.day
51
+ )
52
+ end
53
+ end
54
+
55
+ def last_commit_date
56
+ @last_commit_date ||= project_data_bridge.commits(
57
+ from_date: Date.current - 2.years,
58
+ to_date: Date.current
59
+ ).first[:commit][:author][:date].to_date
60
+ rescue NoMethodError
61
+ :not_found
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,21 @@
1
+ module RepoAnalyzer
2
+ class PowerTypesExtractor < ProjectInfoExtractor
3
+ POWERTYPES = %i{
4
+ commands
5
+ services
6
+ observers
7
+ values
8
+ utils
9
+ clients
10
+ }
11
+
12
+ private
13
+
14
+ def extracted_info
15
+ POWERTYPES.inject({}) do |memo, type|
16
+ memo[type] = project_data_bridge.dir_files("app/#{type}").any?
17
+ memo
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,53 @@
1
+ module RepoAnalyzer
2
+ class ProjectConfigExtractor < ProjectInfoExtractor
3
+ MAKEFILE_CMDS_REGEXP = /^([\w|-]*):/
4
+ RUBOCOP_RULES_REGEXP = %r{^([\w|/]+):}
5
+
6
+ EXCLUDED_MAKEFILE_CMDS = %w{
7
+ run
8
+ help
9
+ }
10
+
11
+ EXCLUDED_RUBOCOP_RULES = %w{
12
+ require
13
+ }
14
+
15
+ private
16
+
17
+ def extracted_info
18
+ {
19
+ simplecov: simplecov?,
20
+ makefile_commands: makefile_commands,
21
+ rubocop_rules: rubocop_rules
22
+ }
23
+ end
24
+
25
+ def simplecov?
26
+ project_data_bridge.file_exist?("spec/simplecov_config.rb")
27
+ end
28
+
29
+ def makefile_commands
30
+ extract_matches_from_content(makefile_content, MAKEFILE_CMDS_REGEXP).reject do |cmd|
31
+ EXCLUDED_MAKEFILE_CMDS.include?(cmd)
32
+ end
33
+ end
34
+
35
+ def rubocop_rules
36
+ extract_matches_from_content(rubocop_rules_content, RUBOCOP_RULES_REGEXP).reject do |rule|
37
+ EXCLUDED_RUBOCOP_RULES.include?(rule)
38
+ end
39
+ end
40
+
41
+ def extract_matches_from_content(content, regexp)
42
+ content.to_s.scan(regexp).flatten.sort
43
+ end
44
+
45
+ def makefile_content
46
+ @makefile_content ||= project_data_bridge.file_content("Makefile")
47
+ end
48
+
49
+ def rubocop_rules_content
50
+ @rubocop_rules_content ||= project_data_bridge.file_content(".rubocop.yml")
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ module RepoAnalyzer
2
+ class ProjectInfoExtractor
3
+ def initialize(project_data_bridge)
4
+ @project_data_bridge = project_data_bridge
5
+ end
6
+
7
+ def extract
8
+ key = self.class.name.underscore.split("/").last.to_sym
9
+ { key => extracted_info.with_indifferent_access }
10
+ end
11
+
12
+ private
13
+
14
+ def extracted_info
15
+ raise RepoAnalyzer::Error.new("extracted_info method not implemented")
16
+ end
17
+
18
+ attr_reader :project_data_bridge
19
+ end
20
+ end
@@ -0,0 +1,80 @@
1
+ module RepoAnalyzer
2
+ class ProjectVersionsExtractor < ProjectInfoExtractor
3
+ CI_VERSION_REGEXP = /^version:\s(\d)/
4
+ RAILS_VERSION_REGEX = /^\s{4}rails \((\d.*)\)/
5
+ PAPERCLIP_VERSION = /^\s{4}paperclip \((\d.*)\)/
6
+ SHRINE_VERSION = /^\s{4}shrine \((\d.*)\)/
7
+ RUBOCOP_VERSION = /^\s{4}rubocop \((\d.*)\)/
8
+ SIDEKIQ_VERSION = /^\s{4}sidekiq \((\d.*)\)/
9
+
10
+ private
11
+
12
+ def extracted_info
13
+ {
14
+ rails_version: gem_version(RAILS_VERSION_REGEX),
15
+ node_version: package_version("engines.node"),
16
+ vue_version: package_version("dependencies.vue"),
17
+ vue_router_version: package_version("dependencies.vue-router"),
18
+ typescript_version: package_version("dependencies.typescript"),
19
+ tailwind_version: tailwind_version,
20
+ paperclip_version: gem_version(PAPERCLIP_VERSION),
21
+ shrine_version: gem_version(SHRINE_VERSION),
22
+ sidekiq_version: gem_version(SIDEKIQ_VERSION),
23
+ circleci_version: circleci_version,
24
+ rubocop_version: gem_version(RUBOCOP_VERSION),
25
+ eslint_version: package_version("devDependencies.eslint"),
26
+ stylelint_version: package_version("devDependencies.stylelint")
27
+ }
28
+ end
29
+
30
+ def tailwind_version
31
+ return "2" if package_json_content.to_s.include?("postcss7-compat")
32
+
33
+ package_version("dependencies.tailwindcss")
34
+ end
35
+
36
+ def gem_version(regexp)
37
+ gemfile_content&.match(regexp)&.captures&.first
38
+ end
39
+
40
+ def package_version(dependency)
41
+ content = package_json_content
42
+ return nil if content.blank?
43
+
44
+ version = JSON.parse(content)
45
+ .dig(*dependency.split(".")).to_s.split("")
46
+ .select do |value|
47
+ value == "." || int?(value)
48
+ end
49
+
50
+ version.pop if version.last == "."
51
+ return version.join("") if version.present?
52
+
53
+ nil
54
+ rescue JSON::ParserError
55
+ nil
56
+ end
57
+
58
+ def circleci_version
59
+ ci_config_content&.match(CI_VERSION_REGEXP)&.captures&.first
60
+ end
61
+
62
+ def int?(value)
63
+ Integer(value)
64
+ rescue ArgumentError
65
+ false
66
+ end
67
+
68
+ def package_json_content
69
+ @package_json_content ||= project_data_bridge.file_content("package.json")
70
+ end
71
+
72
+ def gemfile_content
73
+ @gemfile_content ||= project_data_bridge.file_content("Gemfile.lock")
74
+ end
75
+
76
+ def ci_config_content
77
+ @ci_config_content ||= project_data_bridge.file_content(".circleci/config.yml")
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,36 @@
1
+ module RepoAnalyzer
2
+ class RailsBestPracticesExtractor < ProjectInfoExtractor
3
+ private
4
+
5
+ def extracted_info
6
+ analyzer.analyze
7
+ { errors: errors }
8
+ end
9
+
10
+ def errors
11
+ @errors ||= analyzer.errors.map do |error|
12
+ next unless error.filename.include?(project_data_bridge.project_path)
13
+
14
+ {
15
+ file_path: get_file_path(error),
16
+ message: error.message,
17
+ type: error.type.gsub("RailsBestPractices::", ""),
18
+ url: error.url
19
+ }
20
+ end.compact
21
+ end
22
+
23
+ def get_file_path(error)
24
+ [
25
+ error.filename.gsub(%r{\A\D*#{project_data_bridge.project_path}/}, ""),
26
+ error.line_number
27
+ ].reject(&:blank?).join(":")
28
+ end
29
+
30
+ def analyzer
31
+ @analyzer ||= RailsBestPractices::Analyzer.new(
32
+ project_data_bridge.project_path
33
+ )
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,74 @@
1
+ module RepoAnalyzer
2
+ class RubocopExtractor < ProjectInfoExtractor
3
+ private
4
+
5
+ def extracted_info
6
+ { offenses: grouped_offenses_by_severit_and_cop }
7
+ end
8
+
9
+ def grouped_offenses_by_severit_and_cop
10
+ result = {}
11
+
12
+ grouped_offenses_by_severity.each do |severity, offenses|
13
+ result[severity] = offenses.group_by do |offense|
14
+ offense.delete(:severity)
15
+ offense.delete(:cop)
16
+ end
17
+ end
18
+
19
+ result
20
+ end
21
+
22
+ def grouped_offenses_by_severity
23
+ offenses.group_by do |offense|
24
+ offense[:severity]
25
+ end
26
+ end
27
+
28
+ def offenses
29
+ rubocop_json["files"].inject([]) do |memo, file_data|
30
+ file_data["offenses"].each do |offense|
31
+ memo << {
32
+ path: offense_file_name(file_data["path"], offense),
33
+ cop: offense["cop_name"],
34
+ message: get_message(offense["message"]),
35
+ severity: offense["severity"]
36
+ }
37
+ end
38
+ memo
39
+ end
40
+ end
41
+
42
+ def get_message(message)
43
+ parts = message.to_s.split(": ")
44
+ return parts.first if parts.size == 1
45
+
46
+ parts.shift
47
+ parts.join(": ")
48
+ end
49
+
50
+ def offense_file_name(file_path, offense)
51
+ [
52
+ file_path.gsub(%r{\A\D*#{project_data_bridge.project_path}/}, ""),
53
+ offense['location']['line'].to_s,
54
+ offense['location']['column'].to_s
55
+ ].join(':')
56
+ end
57
+
58
+ def rubocop_json
59
+ options, paths = RuboCop::Options.new.parse(
60
+ [
61
+ "--format", "json",
62
+ "--config", Rails.root.join(".rubocop.yml").to_s,
63
+ project_data_bridge.project_path
64
+ ]
65
+ )
66
+
67
+ runner = RuboCop::Runner.new(options, RuboCop::ConfigStore.new)
68
+ rubocop_result_content = OutputUtils.with_captured_stdout { runner.run(paths) }
69
+ JSON.parse(rubocop_result_content)
70
+ rescue RuboCop::Error
71
+ { "files" => [] }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,87 @@
1
+ module RepoAnalyzer
2
+ class TestsInfoExtractor < ProjectInfoExtractor
3
+ TESTEABLE_RAILS_RESOURCES = %i{
4
+ controllers
5
+ mailers
6
+ models
7
+ jobs
8
+ policies
9
+ commands
10
+ services
11
+ observers
12
+ values
13
+ utils
14
+ clients
15
+ }
16
+
17
+ EXCLUDED_FILES = %w{
18
+ previews
19
+ application_record
20
+ application_controller
21
+ base_controller
22
+ application_job
23
+ concern
24
+ application_mailer
25
+ application_policy
26
+ default_policy
27
+ admin_user_policy
28
+ comment_policy
29
+ page_policy
30
+ }
31
+
32
+ private
33
+
34
+ def extracted_info
35
+ {
36
+ swagger_tests_count: swagger_tests,
37
+ system_tests_count: system_tests_count,
38
+ jest_tests_count: jest_tests_count,
39
+ rails_code_coverage: rails_code_coverage.merge(total: coverage_total)
40
+ }
41
+ end
42
+
43
+ def swagger_tests
44
+ project_data_bridge.dir_files("spec/integration").count
45
+ end
46
+
47
+ def system_tests_count
48
+ project_data_bridge.dir_files("spec/system").count
49
+ end
50
+
51
+ def jest_tests_count
52
+ project_data_bridge.dir_files("app/javascript").count do |file|
53
+ file.include?("spec.js")
54
+ end
55
+ end
56
+
57
+ def rails_code_coverage
58
+ TESTEABLE_RAILS_RESOURCES.inject({}) do |data, resource|
59
+ tests_count = project_data_bridge.dir_files("spec/#{resource}").count do |file|
60
+ valid_file?(file)
61
+ end
62
+ files_count = project_data_bridge.dir_files("app/#{resource}").count do |file|
63
+ valid_file?(file)
64
+ end
65
+ data[resource] = { files_count: files_count, tests_count: tests_count }
66
+ coverage_total[:files_count] += files_count
67
+ coverage_total[:tests_count] += tests_count
68
+ data
69
+ end
70
+ end
71
+
72
+ def valid_file?(file)
73
+ EXCLUDED_FILES.each do |key|
74
+ return false if file.include?(key) || !file.ends_with?(".rb")
75
+ end
76
+
77
+ true
78
+ end
79
+
80
+ def coverage_total
81
+ @coverage_total ||= {
82
+ files_count: 0,
83
+ tests_count: 0
84
+ }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,4 @@
1
+ module RepoAnalyzer
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RepoAnalyzer
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,35 @@
1
+ module RepoAnalyzer
2
+ class ExtractProjectInfoJob < ApplicationJob
3
+ def perform(repo_name)
4
+ project_info = {}
5
+ bridge = RepoAnalyzer::ProjectDataBridge.new(repo_name)
6
+
7
+ for_each_extractor do |extractor|
8
+ extracted_data = extractor.new(bridge).extract
9
+ project_info.merge!(extracted_data)
10
+ end
11
+
12
+ project_info
13
+ end
14
+
15
+ private
16
+
17
+ def for_each_extractor(&block)
18
+ extractors_from_directory.each do |extractor|
19
+ next if extractor == ::RepoAnalyzer::ProjectInfoExtractor
20
+
21
+ block.call(extractor)
22
+ end
23
+ end
24
+
25
+ def extractors_from_directory
26
+ Dir[
27
+ RepoAnalyzer::Engine.root.join("app/extractors/repo_analyzer/*.rb")
28
+ ].map do |path|
29
+ file_name = path.split('/').last
30
+ extractor_name = file_name.split('.').first.camelize
31
+ Object.const_get("::RepoAnalyzer::#{extractor_name}")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require 'zlib'
2
+ require 'base64'
3
+ require 'net/http'
4
+ require 'uri'
5
+
6
+ module RepoAnalyzer
7
+ class PostExtractedInfoJob < ApplicationJob
8
+ def perform(repo_name, project_info)
9
+ encoded_project_info = encode_project_info(project_info)
10
+ response = post_project_info(repo_name, encoded_project_info)
11
+ response.is_a?(Net::HTTPCreated)
12
+ end
13
+
14
+ private
15
+
16
+ def post_project_info(repo_name, encoded_project_info)
17
+ uri = URI(RepoAnalyzer.post_extracted_info_endpoint)
18
+ data = { repo_name: repo_name, project_info: encoded_project_info }.to_json
19
+ headers = { "Content-Type" => "application/json" }
20
+ Net::HTTP.post(uri, data, headers)
21
+ end
22
+
23
+ def encode_project_info(project_info)
24
+ compressed_data = Zlib::Deflate.deflate(project_info.to_json)
25
+ Base64.encode64(compressed_data)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ module RepoAnalyzer
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module RepoAnalyzer
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ class OutputUtils
2
+ def self.with_captured_stdout
3
+ original_stdout = $stdout
4
+ $stdout = StringIO.new
5
+ yield
6
+ $stdout.string
7
+ ensure
8
+ $stdout = original_stdout
9
+ end
10
+ end
@@ -0,0 +1,48 @@
1
+ module RepoAnalyzer
2
+ class ProjectDataBridge
3
+ EXCLUDED_FILE_NAMES = %w{. .. .keep .gitkeep}
4
+
5
+ attr_reader :project_path
6
+
7
+ def initialize(repo_name, project_path = Rails.root.to_s)
8
+ @repo_name = repo_name
9
+ @project_path = project_path
10
+ end
11
+
12
+ def file_exist?(file_path)
13
+ File.exist?("#{project_path}/#{file_path}")
14
+ end
15
+
16
+ def file_content(file_path)
17
+ return unless file_exist?(file_path)
18
+
19
+ File.open("#{project_path}/#{file_path}").read
20
+ end
21
+
22
+ def dir_files(dir_path)
23
+ Dir.glob("#{project_path}/#{dir_path}/**/*").reject do |file_name|
24
+ EXCLUDED_FILE_NAMES.include?(file_name)
25
+ end
26
+ rescue Errno::ENOENT
27
+ []
28
+ end
29
+
30
+ def commits(options = {})
31
+ github_client.commits(repo_name, options)
32
+ end
33
+
34
+ def contributors
35
+ github_client.contributors(repo_name)
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :repo_name
41
+
42
+ def github_client
43
+ @github_client ||= RepoAnalyzer::GithubClient.new(
44
+ RepoAnalyzer.github_personal_token
45
+ )
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Repo analyzer</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "repo_analyzer/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
data/bin/rails ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('..', __dir__)
6
+ ENGINE_PATH = File.expand_path('../lib/repo_analyzer/engine', __dir__)
7
+ APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
8
+
9
+ # Set up gems listed in the Gemfile.
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
11
+ require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
12
+
13
+ require "rails"
14
+ # Pick the frameworks you want:
15
+ require "active_model/railtie"
16
+ require "active_job/railtie"
17
+ require "active_record/railtie"
18
+ require "active_storage/engine"
19
+ require "action_controller/railtie"
20
+ require "action_mailer/railtie"
21
+ require "action_view/railtie"
22
+ require "action_cable/engine"
23
+ require "sprockets/railtie"
24
+ # require "rails/test_unit/railtie"
25
+ require "rails/engine/commands"
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ RepoAnalyzer::Engine.routes.draw do
2
+ end
@@ -0,0 +1,5 @@
1
+ Description:
2
+ Install the engine in host app.
3
+
4
+ Example:
5
+ rails generate repo_analyzer:install
@@ -0,0 +1,19 @@
1
+ class RepoAnalyzer::InstallGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('../templates', __FILE__)
3
+
4
+ def create_initializer
5
+ template "initializer.rb", "config/initializers/repo_analyzer.rb"
6
+ end
7
+
8
+ def mount_routes
9
+ line = "Rails.application.routes.draw do\n"
10
+ inject_into_file "config/routes.rb", after: line do <<-"HERE".gsub(/^ {4}/, '')
11
+ mount RepoAnalyzer::Engine => "/repo_analyzer"
12
+ HERE
13
+ end
14
+ end
15
+
16
+ def copy_engine_migrations
17
+ rake "railties:install:migrations"
18
+ end
19
+ end
@@ -0,0 +1,2 @@
1
+ RepoAnalyzer.setup do |config|
2
+ end
@@ -0,0 +1,15 @@
1
+ module RepoAnalyzer
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace RepoAnalyzer
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec, fixture: false
7
+ g.fixture_replacement :factory_bot, dir: "spec/factories"
8
+ end
9
+
10
+ initializer "initialize" do
11
+ # Require here all your engine's classes.
12
+ require_relative "./example_class"
13
+ end
14
+ end
15
+ end