Skip to content

Commit 194b35c

Browse files
committed
Implement HTML report generation and update report formats
Closes #50 - Allow Skunk to HTML output the results it generates - Added support for generating HTML reports alongside existing JSON format. - Introduced new classes for HTML report generation, including Overview, OverviewData, and FileData. - Updated the Reporter module to include HTML in the report generator formats. - Created a template for the HTML report with a responsive design. - Refactored JSON report generation to improve code organization and clarity.
1 parent 6ea68fe commit 194b35c

8 files changed

Lines changed: 472 additions & 10 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# frozen_string_literal: true
2+
3+
module Skunk
4+
module Generator
5+
module Html
6+
# Data object for individual file information in the HTML overview report
7+
class FileData
8+
attr_reader :file, :skunk_score, :churn_times_cost, :churn, :cost, :coverage
9+
10+
def initialize(module_data)
11+
@file = PathTruncator.truncate(module_data.pathname)
12+
@skunk_score = module_data.skunk_score
13+
@churn_times_cost = module_data.churn_times_cost
14+
@churn = module_data.churn
15+
@cost = module_data.cost.round(2)
16+
@coverage = module_data.coverage.round(2)
17+
end
18+
end
19+
end
20+
end
21+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
require "rubycritic/generators/html/base"
4+
5+
require "skunk/generators/html/path_truncator"
6+
require "skunk/generators/html/overview_data"
7+
8+
module Skunk
9+
module Generator
10+
module Html
11+
# Generates an HTML overview report for the analysed modules.
12+
class Overview < RubyCritic::Generator::Html::Base
13+
def self.erb_template(template_path)
14+
ERB.new(File.read(File.join(TEMPLATES_DIR, template_path)))
15+
end
16+
17+
TEMPLATES_DIR = File.expand_path("templates", __dir__)
18+
TEMPLATE = erb_template("skunk_overview.html.erb")
19+
20+
def initialize(analysed_modules)
21+
super
22+
@analysed_modules = analysed_modules
23+
@data = OverviewData.new(analysed_modules)
24+
end
25+
26+
def file_name
27+
"skunk_overview.html"
28+
end
29+
30+
def render
31+
data = @data
32+
TEMPLATE.result(binding)
33+
end
34+
end
35+
end
36+
end
37+
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# frozen_string_literal: true
2+
3+
require "skunk/generators/html/file_data"
4+
require "skunk/generators/html/path_truncator"
5+
6+
module Skunk
7+
module Generator
8+
module Html
9+
# Data object for the HTML overview report
10+
class OverviewData
11+
attr_reader :generated_at, :skunk_version,
12+
:analysed_modules_count, :skunk_score_total, :skunk_score_average,
13+
:worst_pathname, :worst_score, :files
14+
15+
def initialize(analysed_modules)
16+
@analysed_modules = analysed_modules
17+
@generated_at = Time.now.strftime("%Y-%m-%d %H:%M:%S")
18+
@skunk_version = Skunk::VERSION
19+
20+
@analysed_modules_count = non_test_modules.count
21+
@skunk_score_total = non_test_modules.sum(&:skunk_score)
22+
@skunk_score_average = calculate_average
23+
@worst_pathname = PathTruncator.truncate(find_worst_module&.pathname)
24+
@worst_score = find_worst_module&.skunk_score
25+
@files = build_files
26+
end
27+
28+
private
29+
30+
def non_test_modules
31+
@non_test_modules ||= @analysed_modules.reject do |a_module|
32+
module_path = a_module.pathname.dirname.to_s
33+
module_path.start_with?("test", "spec") || module_path.end_with?("test", "spec")
34+
end
35+
end
36+
37+
def calculate_average
38+
return 0 if @analysed_modules_count.zero?
39+
40+
(@skunk_score_total.to_f / @analysed_modules_count).round(2)
41+
end
42+
43+
def find_worst_module
44+
@find_worst_module ||= sorted_modules.first
45+
end
46+
47+
def sorted_modules
48+
@sorted_modules ||= non_test_modules.sort_by(&:skunk_score).reverse!
49+
end
50+
51+
def build_files
52+
@build_files ||= sorted_modules.map do |module_data|
53+
FileData.new(module_data)
54+
end
55+
end
56+
end
57+
end
58+
end
59+
end
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# frozen_string_literal: true
2+
3+
module Skunk
4+
module Generator
5+
module Html
6+
# Utility class for truncating file paths to show only the relevant project structure
7+
class PathTruncator
8+
# Common project folder names to truncate from
9+
PROJECT_FOLDERS = %w[app lib src test spec features db].freeze
10+
11+
# Truncates a file path to show only the relevant project structure
12+
# starting from the first project folder found
13+
#
14+
# @param file_path [String] The full file path to truncate
15+
# @return [String] The truncated path starting from the project folder
16+
def self.truncate(file_path)
17+
new(file_path).truncate
18+
end
19+
20+
def initialize(file_path)
21+
@file_path = file_path
22+
end
23+
24+
def truncate
25+
return @file_path if @file_path.nil? || @file_path.empty?
26+
27+
path_parts = @file_path.to_s.split("/")
28+
project_folder_index = path_parts.find_index { |part| PROJECT_FOLDERS.include?(part) }
29+
30+
if project_folder_index
31+
relevant_parts = path_parts[project_folder_index..-1]
32+
relevant_parts.join("/")
33+
else
34+
@file_path
35+
end
36+
end
37+
end
38+
end
39+
end
40+
end

0 commit comments

Comments
 (0)