Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ detectors:
FeatureEnvy:
exclude:
- Skunk::Command::StatusReporter#table
- Skunk::Generator::HtmlReport#create_directories_and_files
InstanceVariableAssumption:
exclude:
- Skunk::Cli::Options::Argv
Expand All @@ -22,9 +23,25 @@ detectors:
- initialize
- Skunk::Cli::Application#execute
- Skunk::Cli::Options::Argv#parse
TooManyInstanceVariables:
exclude:
- Skunk::Generator::Html::FileData
- Skunk::Generator::Html::SkunkData
UtilityFunction:
exclude:
- capture_output_streams
- Skunk::Command::Compare#analyse_modified_files
- Skunk::Command::Compare#build_details_path
- Skunk::Command::Shareable#sharing?
- Skunk::Command::Shareable#share_enabled?
- Skunk::Command::StatusSharer#share_enabled?
- Skunk::Command::StatusSharer#share_url_empty?
- Skunk::Configuration#supported_format?
- Skunk::Configuration#supported_formats
ManualDispatch:
exclude:
- Skunk::Config#self.method_missing
- Skunk::Config#self.respond_to_missing?
BooleanParameter:
exclude:
- Skunk::Config#self.respond_to_missing?
12 changes: 6 additions & 6 deletions .rubocop_todo.yml
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we to do anything about the new offenses? Because I saw you added exceptions. Maybe this file needs another update?

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

# Offense count: 1
# Configuration parameters: Severity, Include.
# Include: **/*.gemspec
# Configuration parameters: Severity.
Gemspec/RequiredRubyVersion:
Exclude:
- 'skunk.gemspec'
Expand All @@ -25,22 +24,23 @@ Layout/HeredocIndentation:
Exclude:
- 'lib/skunk/commands/status_reporter.rb'

# Offense count: 1
# Offense count: 2
# Configuration parameters: AllowedParentClasses.
Lint/MissingSuper:
Exclude:
- 'lib/skunk/cli/application.rb'
- 'lib/skunk/generators/html/overview.rb'

# Offense count: 1
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 18

# Offense count: 7
# Offense count: 8
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
# AllowedMethods: refine
Metrics/BlockLength:
Max: 76
Max: 79

# Offense count: 2
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

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

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

Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Skunk is a RubyCritic extension to calculate a SkunkScore for a file or project.
* [Help commands](#help-commands)
* [Generate the SkunkCore for your project](#generate-the-skunkcore-for-your-project)
* [Comparing Feature Branches](#comparing-feature-branches)
* [Configuration](#configuration)
* [Setting Output Formats](#setting-output-formats)
* [Sharing your SkunkScore](#sharing-your-skunkscore)
* [Contributing](#contributing)
* [Sponsorship](#sponsorship)
Expand Down Expand Up @@ -152,6 +154,36 @@ Score: 340.3

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.

## Configuration

### Setting Output Formats

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.

**Supported formats:**
- `:json` - JSON report (default)
- `:html` - HTML report with visual charts and tables
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `:html` - HTML report with visual charts and tables
- `:html` - HTML report with visual tables


```ruby
require 'skunk/config'

# Set multiple formats
Skunk::Config.formats = [:json, :html]

# Add a format to the existing list
Skunk::Config.add_format(:html)

# Remove a format
Skunk::Config.remove_format(:json)

# Check supported formats
Skunk::Config.supported_formats # => [:json, :html]
Skunk::Config.supported_format?(:json) # => true

# Reset to defaults
Skunk::Config.reset
```

## Sharing your SkunkScore

If you want to share the results of your Skunk report with the Ruby community, run:
Expand Down
3 changes: 3 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ puts ARGV.inspect

# Run skunk CLI application with the provided arguments
require "skunk/cli/application"
require "skunk/config"

Skunk::Config.formats = %i[json html]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For testing purposes you can use this script to run skunk against any path and setting any format.

Take into account that for now the console report is generated all the time no matter if you just select :json or :html or both.

> ./bin/console /path/to/project

or

> ./bin/console .

Skunk::Cli::Application.new(ARGV).execute
2 changes: 0 additions & 2 deletions lib/skunk/commands/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ def initialize(options)
#
# @return [Skunk::Command::StatusReporter]
def execute
RubyCritic::Config.formats = [:json]
Copy link
Member Author

@JuanVqz JuanVqz Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was blocking on purpose the generation of more format reports, since now we will have the html report, we want to remove this.


report(critique)

status_reporter
Expand Down
7 changes: 7 additions & 0 deletions lib/skunk/commands/shareable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ def share(reporter)
# @return [Boolean] If the environment is set to share to an external
# service
def sharing?
share_enabled?
end

private

# @return [Boolean] Check if sharing is enabled via environment variable
def share_enabled?
Copy link
Member Author

@JuanVqz JuanVqz Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruby 2.7 CI was running into some segmentation failures because the mocking of ENV["SHARE"].

AI says:

The Problem:
The test was using Object.stub_const(:ENV, env) to stub the ENV constant, which can cause segmentation faults in Ruby 2.7 because ENV is a special system object that shouldn't be stubbed directly.

ENV["SHARE"] == "true"
end
end
Expand Down
14 changes: 12 additions & 2 deletions lib/skunk/commands/status_sharer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def share
response = post_payload

@status_message =
if Net::HTTPOK === response
if response.is_a?(Net::HTTPOK)
Copy link
Member Author

@JuanVqz JuanVqz Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruby 2.6 CI was running into some segmentation failures:

AI says:

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

# Before (segfault in Ruby 2.7):
if Net::HTTPOK === response

# After (safe for all Ruby versions):
if response.is_a?(Net::HTTPOK)

data = JSON.parse response.body
"Shared at: #{File.join(base_url, data['id'])}"
else
Expand Down Expand Up @@ -62,7 +62,17 @@ def json_results

# :reek:UtilityFunction
def not_sharing?
ENV["SHARE"] != "true" && ENV["SHARE_URL"].to_s == ""
!share_enabled? && share_url_empty?
end

# @return [Boolean] Check if sharing is enabled via environment variable
def share_enabled?
ENV["SHARE"] == "true"
end

# @return [Boolean] Check if share URL is empty
def share_url_empty?
ENV["SHARE_URL"].to_s == ""
end

def payload
Expand Down
106 changes: 106 additions & 0 deletions lib/skunk/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# frozen_string_literal: true

module Skunk
# Utility module for format validation
module FormatValidator
# Supported output formats
SUPPORTED_FORMATS = %i[json html].freeze

# Check if a format is supported
# @param format [Symbol] Format to check
# @return [Boolean] True if format is supported
def self.supported_format?(format)
SUPPORTED_FORMATS.include?(format)
end

# Get all supported formats
# @return [Array<Symbol>] All supported formats
def self.supported_formats
SUPPORTED_FORMATS.dup
end
end

# Configuration class for Skunk that supports formats
# Similar to RubyCritic::Configuration but focused only on Skunk's needs
class Configuration
# Default format
DEFAULT_FORMAT = :json

def initialize
@formats = [DEFAULT_FORMAT]
end

def set(options = {})
self.formats = options[:formats] if options.key?(:formats)
end

# Get the configured formats
# @return [Array<Symbol>] Array of format symbols
attr_reader :formats

# Set the formats with validation
# @param format_list [Array<Symbol>, Symbol] Format(s) to set
def formats=(format_list)
format_array = Array(format_list)
@formats = format_array.select { |format| FormatValidator.supported_format?(format) }
@formats = [DEFAULT_FORMAT] if @formats.empty?
end

# Add a format to the existing list
# @param format [Symbol] Format to add
def add_format(format)
return unless FormatValidator.supported_format?(format)

@formats << format unless @formats.include?(format)
end

# Remove a format from the list
# @param format [Symbol] Format to remove
def remove_format(format)
@formats.delete(format)
@formats = [DEFAULT_FORMAT] if @formats.empty?
end

# Check if a format is supported
# @param format [Symbol] Format to check
# @return [Boolean] True if format is supported
def supported_format?(format)
FormatValidator.supported_format?(format)
end

# Get all supported formats
# @return [Array<Symbol>] All supported formats
def supported_formats
FormatValidator.supported_formats
end

# Reset to default configuration
def reset
@formats = [DEFAULT_FORMAT]
end
end

# Config module that delegates to Configuration instance
# Similar to RubyCritic::Config pattern
module Config
def self.configuration
@configuration ||= Configuration.new
end

def self.set(options = {})
configuration.set(options)
end

def self.method_missing(method, *args, &block)
if configuration.respond_to?(method)
configuration.public_send(method, *args, &block)
else
super
end
end

def self.respond_to_missing?(symbol, include_private = false)
configuration.respond_to?(symbol, include_private) || super
end
end
end
21 changes: 21 additions & 0 deletions lib/skunk/generators/html/file_data.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Skunk
module Generator
module Html
# Data object for individual file information in the HTML overview report
class FileData
attr_reader :file, :skunk_score, :churn_times_cost, :churn, :cost, :coverage

def initialize(module_data)
@file = PathTruncator.truncate(module_data.pathname)
Copy link

Copilot AI Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PathTruncator class is not required in this file but is being used. Add require \"skunk/generators/html/path_truncator\" at the top of the file.

Copilot uses AI. Check for mistakes.
@skunk_score = module_data.skunk_score
@churn_times_cost = module_data.churn_times_cost
@churn = module_data.churn
@cost = module_data.cost.round(2)
@coverage = module_data.coverage.round(2)
end
end
end
end
end
35 changes: 35 additions & 0 deletions lib/skunk/generators/html/overview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require "rubycritic/generators/html/base"

require "skunk/generators/html/path_truncator"
require "skunk/generators/html/skunk_data"

module Skunk
module Generator
module Html
# Generates an HTML overview report for the analysed modules.
class Overview < RubyCritic::Generator::Html::Base
def self.erb_template(template_path)
ERB.new(File.read(File.join(TEMPLATES_DIR, template_path)))
end

TEMPLATES_DIR = File.expand_path("templates", __dir__)
TEMPLATE = erb_template("skunk_overview.html.erb")

def initialize(analysed_modules)
@analysed_modules = analysed_modules
@data = SkunkData.new(analysed_modules)
end

def file_name
"skunk_overview.html"
end

def render
TEMPLATE.result(binding)
end
end
end
end
end
Loading