Skip to content

Commit 5380532

Browse files
authored
Implement HTML report generation and update report formats (#123)
* 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. * Improbe table content * Add Table and Card views * Enable HTML report * Set :json format as default * Update json report file name * Fix segmentation fault Issue 1: Unused Variable Warning ✅ File: lib/skunk/generators/html/overview.rb:30 Problem: data = @DaTa was assigned but never used Fix: Removed the unused variable assignment Issue 2: Segmentation Fault ✅ File: lib/skunk/commands/status_sharer.rb:26 Problem: Net::HTTPOK === response causes segfault in Ruby 2.7 Fix: Changed to safer response.is_a?(Net::HTTPOK) comparison The Problem: The ERB template was using a local variable data that was assigned from @DaTa, but Ruby's static analysis couldn't detect that the variable was being used by the ERB template, causing: Ruby 2.6: NameError: undefined local variable or method 'data' Ruby 3.3: Same error Warning: "assigned but unused variable - data" * Fix segmentation failure at shareable class * Update CHANGELOG.md
1 parent 6ea68fe commit 5380532

File tree

20 files changed

+1011
-41
lines changed

20 files changed

+1011
-41
lines changed

.reek.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ detectors:
77
FeatureEnvy:
88
exclude:
99
- Skunk::Command::StatusReporter#table
10+
- Skunk::Generator::HtmlReport#create_directories_and_files
1011
InstanceVariableAssumption:
1112
exclude:
1213
- Skunk::Cli::Options::Argv
@@ -22,9 +23,25 @@ detectors:
2223
- initialize
2324
- Skunk::Cli::Application#execute
2425
- Skunk::Cli::Options::Argv#parse
26+
TooManyInstanceVariables:
27+
exclude:
28+
- Skunk::Generator::Html::FileData
29+
- Skunk::Generator::Html::SkunkData
2530
UtilityFunction:
2631
exclude:
2732
- capture_output_streams
2833
- Skunk::Command::Compare#analyse_modified_files
2934
- Skunk::Command::Compare#build_details_path
3035
- Skunk::Command::Shareable#sharing?
36+
- Skunk::Command::Shareable#share_enabled?
37+
- Skunk::Command::StatusSharer#share_enabled?
38+
- Skunk::Command::StatusSharer#share_url_empty?
39+
- Skunk::Configuration#supported_format?
40+
- Skunk::Configuration#supported_formats
41+
ManualDispatch:
42+
exclude:
43+
- Skunk::Config#self.method_missing
44+
- Skunk::Config#self.respond_to_missing?
45+
BooleanParameter:
46+
exclude:
47+
- Skunk::Config#self.respond_to_missing?

.rubocop_todo.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2025-05-02 20:16:50 UTC using RuboCop version 1.75.4.
3+
# on 2025-10-09 00:04:06 UTC using RuboCop version 1.81.1.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new
77
# versions of RuboCop, may require this file to be generated again.
88

99
# Offense count: 1
10-
# Configuration parameters: Severity, Include.
11-
# Include: **/*.gemspec
10+
# Configuration parameters: Severity.
1211
Gemspec/RequiredRubyVersion:
1312
Exclude:
1413
- 'skunk.gemspec'
@@ -25,22 +24,23 @@ Layout/HeredocIndentation:
2524
Exclude:
2625
- 'lib/skunk/commands/status_reporter.rb'
2726

28-
# Offense count: 1
27+
# Offense count: 2
2928
# Configuration parameters: AllowedParentClasses.
3029
Lint/MissingSuper:
3130
Exclude:
3231
- 'lib/skunk/cli/application.rb'
32+
- 'lib/skunk/generators/html/overview.rb'
3333

3434
# Offense count: 1
3535
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
3636
Metrics/AbcSize:
3737
Max: 18
3838

39-
# Offense count: 7
39+
# Offense count: 8
4040
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
4141
# AllowedMethods: refine
4242
Metrics/BlockLength:
43-
Max: 76
43+
Max: 79
4444

4545
# Offense count: 2
4646
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## main [(unreleased)](https://github.com/fastruby/skunk/compare/v0.5.4...HEAD)
99

10-
* <INSERT YOUR FEATURE OR BUGFIX HERE>
10+
* [FEATURE: Add Skunk HTML Report](https://github.com/fastruby/skunk/pull/123)
11+
* [FEATURE: Add Skunk::Config class](https://github.com/fastruby/skunk/pull/123)
1112

1213
## v0.5.4 / 2025-05-05 [(commits)](https://github.com/fastruby/skunk/compare/v0.5.3...v0.5.4)
1314

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Skunk is a RubyCritic extension to calculate a SkunkScore for a file or project.
1414
* [Help commands](#help-commands)
1515
* [Generate the SkunkCore for your project](#generate-the-skunkcore-for-your-project)
1616
* [Comparing Feature Branches](#comparing-feature-branches)
17+
* [Configuration](#configuration)
18+
* [Setting Output Formats](#setting-output-formats)
1719
* [Sharing your SkunkScore](#sharing-your-skunkscore)
1820
* [Contributing](#contributing)
1921
* [Sponsorship](#sponsorship)
@@ -152,6 +154,36 @@ Score: 340.3
152154

153155
This should give you an idea if you're moving in the direction of maintaining the code quality or not. In this case, the feature branch is decreasing the code quality because it has a higher SkunkScore than the main branch.
154156

157+
## Configuration
158+
159+
### Setting Output Formats
160+
161+
Skunk provides a simple configuration class to control output formats programmatically. You can use `Skunk::Config` to set which formats should be generated when running Skunk.
162+
163+
**Supported formats:**
164+
- `:json` - JSON report (default)
165+
- `:html` - HTML report with visual charts and tables
166+
167+
```ruby
168+
require 'skunk/config'
169+
170+
# Set multiple formats
171+
Skunk::Config.formats = [:json, :html]
172+
173+
# Add a format to the existing list
174+
Skunk::Config.add_format(:html)
175+
176+
# Remove a format
177+
Skunk::Config.remove_format(:json)
178+
179+
# Check supported formats
180+
Skunk::Config.supported_formats # => [:json, :html]
181+
Skunk::Config.supported_format?(:json) # => true
182+
183+
# Reset to defaults
184+
Skunk::Config.reset
185+
```
186+
155187
## Sharing your SkunkScore
156188

157189
If you want to share the results of your Skunk report with the Ruby community, run:

bin/console

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ puts ARGV.inspect
1111

1212
# Run skunk CLI application with the provided arguments
1313
require "skunk/cli/application"
14+
require "skunk/config"
15+
16+
Skunk::Config.formats = %i[json html]
1417
Skunk::Cli::Application.new(ARGV).execute

lib/skunk/commands/default.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ def initialize(options)
2626
#
2727
# @return [Skunk::Command::StatusReporter]
2828
def execute
29-
RubyCritic::Config.formats = [:json]
30-
3129
report(critique)
3230

3331
status_reporter

lib/skunk/commands/shareable.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ def share(reporter)
1818
# @return [Boolean] If the environment is set to share to an external
1919
# service
2020
def sharing?
21+
share_enabled?
22+
end
23+
24+
private
25+
26+
# @return [Boolean] Check if sharing is enabled via environment variable
27+
def share_enabled?
2128
ENV["SHARE"] == "true"
2229
end
2330
end

lib/skunk/commands/status_sharer.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def share
2323
response = post_payload
2424

2525
@status_message =
26-
if Net::HTTPOK === response
26+
if response.is_a?(Net::HTTPOK)
2727
data = JSON.parse response.body
2828
"Shared at: #{File.join(base_url, data['id'])}"
2929
else
@@ -62,7 +62,17 @@ def json_results
6262

6363
# :reek:UtilityFunction
6464
def not_sharing?
65-
ENV["SHARE"] != "true" && ENV["SHARE_URL"].to_s == ""
65+
!share_enabled? && share_url_empty?
66+
end
67+
68+
# @return [Boolean] Check if sharing is enabled via environment variable
69+
def share_enabled?
70+
ENV["SHARE"] == "true"
71+
end
72+
73+
# @return [Boolean] Check if share URL is empty
74+
def share_url_empty?
75+
ENV["SHARE_URL"].to_s == ""
6676
end
6777

6878
def payload

lib/skunk/config.rb

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# frozen_string_literal: true
2+
3+
module Skunk
4+
# Utility module for format validation
5+
module FormatValidator
6+
# Supported output formats
7+
SUPPORTED_FORMATS = %i[json html].freeze
8+
9+
# Check if a format is supported
10+
# @param format [Symbol] Format to check
11+
# @return [Boolean] True if format is supported
12+
def self.supported_format?(format)
13+
SUPPORTED_FORMATS.include?(format)
14+
end
15+
16+
# Get all supported formats
17+
# @return [Array<Symbol>] All supported formats
18+
def self.supported_formats
19+
SUPPORTED_FORMATS.dup
20+
end
21+
end
22+
23+
# Configuration class for Skunk that supports formats
24+
# Similar to RubyCritic::Configuration but focused only on Skunk's needs
25+
class Configuration
26+
# Default format
27+
DEFAULT_FORMAT = :json
28+
29+
def initialize
30+
@formats = [DEFAULT_FORMAT]
31+
end
32+
33+
def set(options = {})
34+
self.formats = options[:formats] if options.key?(:formats)
35+
end
36+
37+
# Get the configured formats
38+
# @return [Array<Symbol>] Array of format symbols
39+
attr_reader :formats
40+
41+
# Set the formats with validation
42+
# @param format_list [Array<Symbol>, Symbol] Format(s) to set
43+
def formats=(format_list)
44+
format_array = Array(format_list)
45+
@formats = format_array.select { |format| FormatValidator.supported_format?(format) }
46+
@formats = [DEFAULT_FORMAT] if @formats.empty?
47+
end
48+
49+
# Add a format to the existing list
50+
# @param format [Symbol] Format to add
51+
def add_format(format)
52+
return unless FormatValidator.supported_format?(format)
53+
54+
@formats << format unless @formats.include?(format)
55+
end
56+
57+
# Remove a format from the list
58+
# @param format [Symbol] Format to remove
59+
def remove_format(format)
60+
@formats.delete(format)
61+
@formats = [DEFAULT_FORMAT] if @formats.empty?
62+
end
63+
64+
# Check if a format is supported
65+
# @param format [Symbol] Format to check
66+
# @return [Boolean] True if format is supported
67+
def supported_format?(format)
68+
FormatValidator.supported_format?(format)
69+
end
70+
71+
# Get all supported formats
72+
# @return [Array<Symbol>] All supported formats
73+
def supported_formats
74+
FormatValidator.supported_formats
75+
end
76+
77+
# Reset to default configuration
78+
def reset
79+
@formats = [DEFAULT_FORMAT]
80+
end
81+
end
82+
83+
# Config module that delegates to Configuration instance
84+
# Similar to RubyCritic::Config pattern
85+
module Config
86+
def self.configuration
87+
@configuration ||= Configuration.new
88+
end
89+
90+
def self.set(options = {})
91+
configuration.set(options)
92+
end
93+
94+
def self.method_missing(method, *args, &block)
95+
if configuration.respond_to?(method)
96+
configuration.public_send(method, *args, &block)
97+
else
98+
super
99+
end
100+
end
101+
102+
def self.respond_to_missing?(symbol, include_private = false)
103+
configuration.respond_to?(symbol, include_private) || super
104+
end
105+
end
106+
end
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

0 commit comments

Comments
 (0)