skunk 0.4.2 → 0.5.0
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 +4 -4
 - data/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
 - data/.github/ISSUE_TEMPLATE/feature_request.md +47 -0
 - data/.github/workflows/main.yml +74 -4
 - data/.github/workflows/skunk.yml +8 -3
 - data/.gitignore +1 -0
 - data/.rubocop_todo.yml +39 -17
 - data/CHANGELOG.md +23 -16
 - data/CODEOWNERS +5 -0
 - data/Gemfile-Ruby-2-4 +10 -0
 - data/README.md +64 -20
 - data/fastruby-logo.png +0 -0
 - data/lib/skunk.rb +1 -1
 - data/lib/skunk/cli/application.rb +34 -4
 - data/lib/skunk/cli/commands/base.rb +2 -0
 - data/lib/skunk/cli/commands/compare.rb +10 -6
 - data/lib/skunk/cli/commands/compare_score.rb +39 -0
 - data/lib/skunk/cli/commands/default.rb +22 -1
 - data/lib/skunk/cli/commands/help.rb +11 -1
 - data/lib/skunk/cli/commands/status_reporter.rb +16 -13
 - data/lib/skunk/cli/commands/status_sharer.rb +100 -0
 - data/lib/skunk/cli/options/argv.rb +11 -0
 - data/lib/skunk/rubycritic/analysed_module.rb +29 -9
 - data/lib/skunk/rubycritic/analysed_modules_collection.rb +2 -2
 - data/lib/skunk/version.rb +1 -1
 - data/logo.png +0 -0
 - data/samples/engines/spec/nested_sample_spec.rb +5 -0
 - data/samples/rubycritic/analysed_module.rb +9 -9
 - data/skunk.gemspec +13 -7
 - metadata +100 -28
 - data/.travis.yml +0 -5
 - data/Gemfile.lock +0 -119
 
    
        data/fastruby-logo.png
    ADDED
    
    | 
         Binary file 
     | 
    
        data/lib/skunk.rb
    CHANGED
    
    
| 
         @@ -7,24 +7,54 @@ require "skunk" 
     | 
|
| 
       7 
7 
     | 
    
         
             
            require "skunk/rubycritic/analysed_module"
         
     | 
| 
       8 
8 
     | 
    
         
             
            require "skunk/cli/options"
         
     | 
| 
       9 
9 
     | 
    
         
             
            require "skunk/cli/command_factory"
         
     | 
| 
      
 10 
     | 
    
         
            +
            require "skunk/cli/commands/status_sharer"
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
12 
     | 
    
         
             
            module Skunk
         
     | 
| 
       12 
13 
     | 
    
         
             
              module Cli
         
     | 
| 
       13 
14 
     | 
    
         
             
                # Knows how to execute command line commands
         
     | 
| 
      
 15 
     | 
    
         
            +
                # :reek:InstanceVariableAssumption
         
     | 
| 
       14 
16 
     | 
    
         
             
                class Application < RubyCritic::Cli::Application
         
     | 
| 
      
 17 
     | 
    
         
            +
                  COVERAGE_FILE = "coverage/.resultset.json"
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
       15 
19 
     | 
    
         
             
                  def initialize(argv)
         
     | 
| 
       16 
20 
     | 
    
         
             
                    @options = Skunk::Cli::Options.new(argv)
         
     | 
| 
       17 
21 
     | 
    
         
             
                  end
         
     | 
| 
       18 
22 
     | 
    
         | 
| 
      
 23 
     | 
    
         
            +
                  # :reek:UncommunicativeVariableName
         
     | 
| 
       19 
24 
     | 
    
         
             
                  def execute
         
     | 
| 
       20 
     | 
    
         
            -
                     
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
      
 25 
     | 
    
         
            +
                    warn_coverage_info unless File.exist?(COVERAGE_FILE)
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    # :reek:NilCheck
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @parsed_options = @options.parse.to_h
         
     | 
| 
      
 29 
     | 
    
         
            +
                    command = Skunk::Cli::CommandFactory.create(@parsed_options)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    reporter = command.execute
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
       22 
32 
     | 
    
         
             
                    print(reporter.status_message)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    share_status_message = command.share(reporter)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    print(share_status_message)
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
       23 
36 
     | 
    
         
             
                    reporter.status
         
     | 
| 
       24 
     | 
    
         
            -
                  rescue OptionParser::InvalidOption =>  
     | 
| 
       25 
     | 
    
         
            -
                    warn "Error: #{ 
     | 
| 
      
 37 
     | 
    
         
            +
                  rescue OptionParser::InvalidOption => e
         
     | 
| 
      
 38 
     | 
    
         
            +
                    warn "Error: #{e}"
         
     | 
| 
       26 
39 
     | 
    
         
             
                    STATUS_ERROR
         
     | 
| 
       27 
40 
     | 
    
         
             
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  private
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  def warn_coverage_info
         
     | 
| 
      
 45 
     | 
    
         
            +
                    warn "warning: Couldn't find coverage info at #{COVERAGE_FILE}."
         
     | 
| 
      
 46 
     | 
    
         
            +
                    warn "warning: Having no coverage metrics will make your SkunkScore worse."
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  # :reek:NilCheck
         
     | 
| 
      
 50 
     | 
    
         
            +
                  def print(message)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    filename = @parsed_options[:output_filename]
         
     | 
| 
      
 52 
     | 
    
         
            +
                    if filename.nil?
         
     | 
| 
      
 53 
     | 
    
         
            +
                      $stdout.puts(message)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    else
         
     | 
| 
      
 55 
     | 
    
         
            +
                      File.open(filename, "a") { |file| file << message }
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
       28 
58 
     | 
    
         
             
                end
         
     | 
| 
       29 
59 
     | 
    
         
             
              end
         
     | 
| 
       30 
60 
     | 
    
         
             
            end
         
     | 
| 
         @@ -3,17 +3,18 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            require "rubycritic/commands/compare"
         
     | 
| 
       4 
4 
     | 
    
         
             
            require "skunk/rubycritic/analysed_modules_collection"
         
     | 
| 
       5 
5 
     | 
    
         
             
            require "skunk/cli/commands/output"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require "skunk/cli/commands/compare_score"
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
       7 
8 
     | 
    
         
             
            # nodoc #
         
     | 
| 
       8 
9 
     | 
    
         
             
            module Skunk
         
     | 
| 
       9 
10 
     | 
    
         
             
              module Command
         
     | 
| 
       10 
     | 
    
         
            -
                # Knows how to compare two branches and their  
     | 
| 
      
 11 
     | 
    
         
            +
                # Knows how to compare two branches and their skunk score average
         
     | 
| 
       11 
12 
     | 
    
         
             
                class Compare < RubyCritic::Command::Compare
         
     | 
| 
       12 
13 
     | 
    
         
             
                  # switch branch and analyse files but don't generate a report
         
     | 
| 
       13 
14 
     | 
    
         
             
                  def analyse_branch(branch)
         
     | 
| 
       14 
15 
     | 
    
         
             
                    ::RubyCritic::SourceControlSystem::Git.switch_branch(::RubyCritic::Config.send(branch))
         
     | 
| 
       15 
16 
     | 
    
         
             
                    critic = critique(branch)
         
     | 
| 
       16 
     | 
    
         
            -
                    ::RubyCritic::Config.send(:"#{branch}_score=", critic. 
     | 
| 
      
 17 
     | 
    
         
            +
                    ::RubyCritic::Config.send(:"#{branch}_score=", critic.skunk_score_average)
         
     | 
| 
       17 
18 
     | 
    
         
             
                    ::RubyCritic::Config.root = branch_directory(branch)
         
     | 
| 
       18 
19 
     | 
    
         
             
                  end
         
     | 
| 
       19 
20 
     | 
    
         | 
| 
         @@ -29,10 +30,13 @@ module Skunk 
     | 
|
| 
       29 
30 
     | 
    
         | 
| 
       30 
31 
     | 
    
         
             
                  # create a txt file with the branch score details
         
     | 
| 
       31 
32 
     | 
    
         
             
                  def build_details
         
     | 
| 
       32 
     | 
    
         
            -
                    details =  
     | 
| 
       33 
     | 
    
         
            -
                       
     | 
| 
       34 
     | 
    
         
            -
                       
     | 
| 
       35 
     | 
    
         
            -
                       
     | 
| 
      
 33 
     | 
    
         
            +
                    details = CompareScore.new(
         
     | 
| 
      
 34 
     | 
    
         
            +
                      ::RubyCritic::Config.base_branch,
         
     | 
| 
      
 35 
     | 
    
         
            +
                      ::RubyCritic::Config.feature_branch,
         
     | 
| 
      
 36 
     | 
    
         
            +
                      ::RubyCritic::Config.base_branch_score.to_f.round(2),
         
     | 
| 
      
 37 
     | 
    
         
            +
                      ::RubyCritic::Config.feature_branch_score.to_f.round(2)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    ).message
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
       36 
40 
     | 
    
         
             
                    Skunk::Command::Output.create_directory(::RubyCritic::Config.compare_root_directory)
         
     | 
| 
       37 
41 
     | 
    
         
             
                    File.open(build_details_path, "w") { |file| file.write(details) }
         
     | 
| 
       38 
42 
     | 
    
         
             
                    puts details
         
     | 
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # nodoc #
         
     | 
| 
      
 4 
     | 
    
         
            +
            module Skunk
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Command
         
     | 
| 
      
 6 
     | 
    
         
            +
                # Knows how to describe score evolution between two branches
         
     | 
| 
      
 7 
     | 
    
         
            +
                class CompareScore
         
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize(base_branch, feature_branch, base_branch_score, feature_branch_score)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @base_branch = base_branch
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @feature_branch = feature_branch
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @base_branch_score = base_branch_score
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @feature_branch_score = feature_branch_score
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def message
         
     | 
| 
      
 16 
     | 
    
         
            +
                    "Base branch (#{@base_branch}) "\
         
     | 
| 
      
 17 
     | 
    
         
            +
                      "average skunk score: #{@base_branch_score} \n"\
         
     | 
| 
      
 18 
     | 
    
         
            +
                      "Feature branch (#{@feature_branch}) "\
         
     | 
| 
      
 19 
     | 
    
         
            +
                      "average skunk score: #{@feature_branch_score} \n"\
         
     | 
| 
      
 20 
     | 
    
         
            +
                      "#{score_evolution_message}"
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def score_evolution_message
         
     | 
| 
      
 24 
     | 
    
         
            +
                    "Skunk score average is #{score_evolution} #{score_evolution_appreciation} \n"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def score_evolution_appreciation
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @feature_branch_score > @base_branch_score ? "worse" : "better"
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def score_evolution
         
     | 
| 
      
 32 
     | 
    
         
            +
                    return "Infinitely" if @base_branch_score.zero?
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    precentage = (100 * (@base_branch_score - @feature_branch_score) / @base_branch_score)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    "#{precentage.round(0).abs}%"
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -16,20 +16,41 @@ module Skunk 
     | 
|
| 
       16 
16 
     | 
    
         
             
                  class Default < RubyCritic::Command::Default
         
     | 
| 
       17 
17 
     | 
    
         
             
                    def initialize(options)
         
     | 
| 
       18 
18 
     | 
    
         
             
                      super
         
     | 
| 
       19 
     | 
    
         
            -
                      @ 
     | 
| 
      
 19 
     | 
    
         
            +
                      @options = options
         
     | 
| 
      
 20 
     | 
    
         
            +
                      @status_reporter = Skunk::Command::StatusReporter.new(options)
         
     | 
| 
       20 
21 
     | 
    
         
             
                    end
         
     | 
| 
       21 
22 
     | 
    
         | 
| 
      
 23 
     | 
    
         
            +
                    # It generates a report and it returns an instance of
         
     | 
| 
      
 24 
     | 
    
         
            +
                    # Skunk::Command::StatusReporter
         
     | 
| 
      
 25 
     | 
    
         
            +
                    #
         
     | 
| 
      
 26 
     | 
    
         
            +
                    # @return [Skunk::Command::StatusReporter]
         
     | 
| 
       22 
27 
     | 
    
         
             
                    def execute
         
     | 
| 
       23 
28 
     | 
    
         
             
                      RubyCritic::Config.formats = []
         
     | 
| 
       24 
29 
     | 
    
         | 
| 
       25 
30 
     | 
    
         
             
                      report(critique)
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
       26 
32 
     | 
    
         
             
                      status_reporter
         
     | 
| 
       27 
33 
     | 
    
         
             
                    end
         
     | 
| 
       28 
34 
     | 
    
         | 
| 
      
 35 
     | 
    
         
            +
                    # It connects the Skunk::Command::StatusReporter with the collection
         
     | 
| 
      
 36 
     | 
    
         
            +
                    # of analysed modules.
         
     | 
| 
      
 37 
     | 
    
         
            +
                    #
         
     | 
| 
      
 38 
     | 
    
         
            +
                    # @param [RubyCritic::AnalysedModulesCollection] A collection of analysed modules
         
     | 
| 
       29 
39 
     | 
    
         
             
                    def report(analysed_modules)
         
     | 
| 
       30 
40 
     | 
    
         
             
                      status_reporter.analysed_modules = analysed_modules
         
     | 
| 
       31 
41 
     | 
    
         
             
                      status_reporter.score = analysed_modules.score
         
     | 
| 
       32 
42 
     | 
    
         
             
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    # It shares the report using SHARE_URL or https://skunk.fastruby.io. It
         
     | 
| 
      
 45 
     | 
    
         
            +
                    # will post all results in JSON format and return a status message.
         
     | 
| 
      
 46 
     | 
    
         
            +
                    #
         
     | 
| 
      
 47 
     | 
    
         
            +
                    # @param [Skunk::Command::StatusReporter] A status reporter with analysed modules
         
     | 
| 
      
 48 
     | 
    
         
            +
                    # :reek:FeatureEnvy
         
     | 
| 
      
 49 
     | 
    
         
            +
                    def share(reporter)
         
     | 
| 
      
 50 
     | 
    
         
            +
                      sharer = Skunk::Command::StatusSharer.new(@options)
         
     | 
| 
      
 51 
     | 
    
         
            +
                      sharer.status_reporter = reporter
         
     | 
| 
      
 52 
     | 
    
         
            +
                      sharer.share
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
       33 
54 
     | 
    
         
             
                  end
         
     | 
| 
       34 
55 
     | 
    
         
             
                end
         
     | 
| 
       35 
56 
     | 
    
         
             
              end
         
     | 
| 
         @@ -6,7 +6,17 @@ require "rubycritic/commands/help" 
     | 
|
| 
       6 
6 
     | 
    
         
             
            module Skunk
         
     | 
| 
       7 
7 
     | 
    
         
             
              module Cli
         
     | 
| 
       8 
8 
     | 
    
         
             
                module Command
         
     | 
| 
       9 
     | 
    
         
            -
                   
     | 
| 
      
 9 
     | 
    
         
            +
                  # Knows how to guide user into using `skunk` properly
         
     | 
| 
      
 10 
     | 
    
         
            +
                  class Help < Skunk::Cli::Command::Base
         
     | 
| 
      
 11 
     | 
    
         
            +
                    # Outputs a help message
         
     | 
| 
      
 12 
     | 
    
         
            +
                    def execute
         
     | 
| 
      
 13 
     | 
    
         
            +
                      puts options[:help_text]
         
     | 
| 
      
 14 
     | 
    
         
            +
                      status_reporter
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    private
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    attr_reader :options, :status_reporter
         
     | 
| 
       10 
20 
     | 
    
         
             
                  end
         
     | 
| 
       11 
21 
     | 
    
         
             
                end
         
     | 
| 
       12 
22 
     | 
    
         
             
              end
         
     | 
| 
         @@ -10,21 +10,23 @@ module Skunk 
     | 
|
| 
       10 
10 
     | 
    
         
             
                class StatusReporter < RubyCritic::Command::StatusReporter
         
     | 
| 
       11 
11 
     | 
    
         
             
                  attr_accessor :analysed_modules
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                  HEADINGS = %w[file  
     | 
| 
      
 13 
     | 
    
         
            +
                  HEADINGS = %w[file skunk_score churn_times_cost churn cost coverage].freeze
         
     | 
| 
       14 
14 
     | 
    
         
             
                  HEADINGS_WITHOUT_FILE = HEADINGS - %w[file]
         
     | 
| 
       15 
15 
     | 
    
         
             
                  HEADINGS_WITHOUT_FILE_WIDTH = HEADINGS_WITHOUT_FILE.size * 17 # padding
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
       17 
17 
     | 
    
         
             
                  TEMPLATE = ERB.new(<<-TEMPL
         
     | 
| 
       18 
18 
     | 
    
         
             
            <%= _ttable %>\n
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
      
 19 
     | 
    
         
            +
            SkunkScore Total: <%= total_skunk_score %>
         
     | 
| 
       20 
20 
     | 
    
         
             
            Modules Analysed: <%= analysed_modules_count %>
         
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
            <% if worst %>Worst  
     | 
| 
      
 21 
     | 
    
         
            +
            SkunkScore Average: <%= skunk_score_average %>
         
     | 
| 
      
 22 
     | 
    
         
            +
            <% if worst %>Worst SkunkScore: <%= worst.skunk_score %> (<%= worst.pathname %>)<% end %>
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            Generated with Skunk v<%= Skunk::VERSION %>
         
     | 
| 
       23 
25 
     | 
    
         
             
            TEMPL
         
     | 
| 
       24 
26 
     | 
    
         
             
                                    )
         
     | 
| 
       25 
27 
     | 
    
         | 
| 
       26 
28 
     | 
    
         
             
                  # Returns a status message with a table of all analysed_modules and
         
     | 
| 
       27 
     | 
    
         
            -
                  # a  
     | 
| 
      
 29 
     | 
    
         
            +
                  # a skunk score average
         
     | 
| 
       28 
30 
     | 
    
         
             
                  def update_status_message
         
     | 
| 
       29 
31 
     | 
    
         
             
                    opts = table_options.merge(headings: HEADINGS, rows: table)
         
     | 
| 
       30 
32 
     | 
    
         | 
| 
         @@ -41,7 +43,8 @@ TEMPL 
     | 
|
| 
       41 
43 
     | 
    
         | 
| 
       42 
44 
     | 
    
         
             
                  def non_test_modules
         
     | 
| 
       43 
45 
     | 
    
         
             
                    @non_test_modules ||= analysed_modules.reject do |a_module|
         
     | 
| 
       44 
     | 
    
         
            -
                      a_module.pathname.to_s 
     | 
| 
      
 46 
     | 
    
         
            +
                      module_path = a_module.pathname.dirname.to_s
         
     | 
| 
      
 47 
     | 
    
         
            +
                      module_path.start_with?("test", "spec") || module_path.end_with?("test", "spec")
         
     | 
| 
       45 
48 
     | 
    
         
             
                    end
         
     | 
| 
       46 
49 
     | 
    
         
             
                  end
         
     | 
| 
       47 
50 
     | 
    
         | 
| 
         @@ -50,21 +53,21 @@ TEMPL 
     | 
|
| 
       50 
53 
     | 
    
         
             
                  end
         
     | 
| 
       51 
54 
     | 
    
         | 
| 
       52 
55 
     | 
    
         
             
                  def sorted_modules
         
     | 
| 
       53 
     | 
    
         
            -
                    @sorted_modules ||= non_test_modules.sort_by(&: 
     | 
| 
      
 56 
     | 
    
         
            +
                    @sorted_modules ||= non_test_modules.sort_by(&:skunk_score).reverse!
         
     | 
| 
       54 
57 
     | 
    
         
             
                  end
         
     | 
| 
       55 
58 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
                  def  
     | 
| 
       57 
     | 
    
         
            -
                    @ 
     | 
| 
      
 59 
     | 
    
         
            +
                  def total_skunk_score
         
     | 
| 
      
 60 
     | 
    
         
            +
                    @total_skunk_score ||= non_test_modules.sum(&:skunk_score)
         
     | 
| 
       58 
61 
     | 
    
         
             
                  end
         
     | 
| 
       59 
62 
     | 
    
         | 
| 
       60 
63 
     | 
    
         
             
                  def total_churn_times_cost
         
     | 
| 
       61 
     | 
    
         
            -
                    non_test_modules. 
     | 
| 
      
 64 
     | 
    
         
            +
                    non_test_modules.sum(&:churn_times_cost)
         
     | 
| 
       62 
65 
     | 
    
         
             
                  end
         
     | 
| 
       63 
66 
     | 
    
         | 
| 
       64 
     | 
    
         
            -
                  def  
     | 
| 
      
 67 
     | 
    
         
            +
                  def skunk_score_average
         
     | 
| 
       65 
68 
     | 
    
         
             
                    return 0 if analysed_modules_count.zero?
         
     | 
| 
       66 
69 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
                    ( 
     | 
| 
      
 70 
     | 
    
         
            +
                    (total_skunk_score.to_d / analysed_modules_count).to_f.round(2)
         
     | 
| 
       68 
71 
     | 
    
         
             
                  end
         
     | 
| 
       69 
72 
     | 
    
         | 
| 
       70 
73 
     | 
    
         
             
                  def table_options
         
     | 
| 
         @@ -81,7 +84,7 @@ TEMPL 
     | 
|
| 
       81 
84 
     | 
    
         
             
                    sorted_modules.map do |a_mod|
         
     | 
| 
       82 
85 
     | 
    
         
             
                      [
         
     | 
| 
       83 
86 
     | 
    
         
             
                        a_mod.pathname,
         
     | 
| 
       84 
     | 
    
         
            -
                        a_mod. 
     | 
| 
      
 87 
     | 
    
         
            +
                        a_mod.skunk_score,
         
     | 
| 
       85 
88 
     | 
    
         
             
                        a_mod.churn_times_cost,
         
     | 
| 
       86 
89 
     | 
    
         
             
                        a_mod.churn,
         
     | 
| 
       87 
90 
     | 
    
         
             
                        a_mod.cost.round(2),
         
     | 
| 
         @@ -0,0 +1,100 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "net/http"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "net/https"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "json"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            require "skunk/cli/commands/status_reporter"
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            module Skunk
         
     | 
| 
      
 10 
     | 
    
         
            +
              module Command
         
     | 
| 
      
 11 
     | 
    
         
            +
                # Knows how to share status to an API
         
     | 
| 
      
 12 
     | 
    
         
            +
                class StatusSharer < Skunk::Command::StatusReporter
         
     | 
| 
      
 13 
     | 
    
         
            +
                  attr_reader :status_message
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  DEFAULT_URL = "https://skunk.fastruby.io"
         
     | 
| 
      
 16 
     | 
    
         
            +
                  def status_reporter=(status_reporter)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    self.analysed_modules = status_reporter.analysed_modules
         
     | 
| 
      
 18 
     | 
    
         
            +
                    self.score = analysed_modules.score
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def share
         
     | 
| 
      
 22 
     | 
    
         
            +
                    return "" if not_sharing?
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    response = post_payload
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                    @status_message =
         
     | 
| 
      
 27 
     | 
    
         
            +
                      if Net::HTTPOK === response
         
     | 
| 
      
 28 
     | 
    
         
            +
                        data = JSON.parse response.body
         
     | 
| 
      
 29 
     | 
    
         
            +
                        "Shared at: #{File.join(base_url, data['id'])}"
         
     | 
| 
      
 30 
     | 
    
         
            +
                      else
         
     | 
| 
      
 31 
     | 
    
         
            +
                        "Error sharing report: #{response}"
         
     | 
| 
      
 32 
     | 
    
         
            +
                      end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  private
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  # :reek:UtilityFunction
         
     | 
| 
      
 38 
     | 
    
         
            +
                  def base_url
         
     | 
| 
      
 39 
     | 
    
         
            +
                    ENV["SHARE_URL"] || DEFAULT_URL
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  def json_summary
         
     | 
| 
      
 43 
     | 
    
         
            +
                    result = {
         
     | 
| 
      
 44 
     | 
    
         
            +
                      total_skunk_score: total_skunk_score,
         
     | 
| 
      
 45 
     | 
    
         
            +
                      analysed_modules_count: analysed_modules_count,
         
     | 
| 
      
 46 
     | 
    
         
            +
                      skunk_score_average: skunk_score_average,
         
     | 
| 
      
 47 
     | 
    
         
            +
                      skunk_version: Skunk::VERSION
         
     | 
| 
      
 48 
     | 
    
         
            +
                    }
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    if worst
         
     | 
| 
      
 51 
     | 
    
         
            +
                      result[:worst_skunk_score] = {
         
     | 
| 
      
 52 
     | 
    
         
            +
                        file: worst.pathname.to_s,
         
     | 
| 
      
 53 
     | 
    
         
            +
                        skunk_score: worst.skunk_score
         
     | 
| 
      
 54 
     | 
    
         
            +
                      }
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    result
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  def json_results
         
     | 
| 
      
 61 
     | 
    
         
            +
                    sorted_modules.map(&:to_hash)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  # :reek:UtilityFunction
         
     | 
| 
      
 65 
     | 
    
         
            +
                  def not_sharing?
         
     | 
| 
      
 66 
     | 
    
         
            +
                    ENV["SHARE"] != "true" && ENV["SHARE_URL"].to_s == ""
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  def payload
         
     | 
| 
      
 70 
     | 
    
         
            +
                    JSON.generate(
         
     | 
| 
      
 71 
     | 
    
         
            +
                      "entries" => json_results,
         
     | 
| 
      
 72 
     | 
    
         
            +
                      "summary" => json_summary,
         
     | 
| 
      
 73 
     | 
    
         
            +
                      "options" => {
         
     | 
| 
      
 74 
     | 
    
         
            +
                        "compare" => "false"
         
     | 
| 
      
 75 
     | 
    
         
            +
                      }
         
     | 
| 
      
 76 
     | 
    
         
            +
                    )
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  # :reek:TooManyStatements
         
     | 
| 
      
 80 
     | 
    
         
            +
                  def post_payload
         
     | 
| 
      
 81 
     | 
    
         
            +
                    req = Net::HTTP::Post.new(url)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    req.body = payload
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                    http = Net::HTTP.new(url.hostname, url.port)
         
     | 
| 
      
 85 
     | 
    
         
            +
                    if url.scheme == "https"
         
     | 
| 
      
 86 
     | 
    
         
            +
                      http.use_ssl = true
         
     | 
| 
      
 87 
     | 
    
         
            +
                      http.ssl_version = :TLSv1_2
         
     | 
| 
      
 88 
     | 
    
         
            +
                    end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                    http.start do |connection|
         
     | 
| 
      
 91 
     | 
    
         
            +
                      connection.request req
         
     | 
| 
      
 92 
     | 
    
         
            +
                    end
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  def url
         
     | 
| 
      
 96 
     | 
    
         
            +
                    URI(File.join(base_url, "reports"))
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
              end
         
     | 
| 
      
 100 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -9,6 +9,9 @@ module Skunk 
     | 
|
| 
       9 
9 
     | 
    
         
             
                  # Extends RubyCritic::Cli::Options::Argv to parse a subset of the
         
     | 
| 
       10 
10 
     | 
    
         
             
                  # parameters accepted by RubyCritic
         
     | 
| 
       11 
11 
     | 
    
         
             
                  class Argv < RubyCritic::Cli::Options::Argv
         
     | 
| 
      
 12 
     | 
    
         
            +
                    # :reek:Attribute
         
     | 
| 
      
 13 
     | 
    
         
            +
                    attr_accessor :output_filename
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
       12 
15 
     | 
    
         
             
                    def parse # rubocop:disable Metrics/MethodLength
         
     | 
| 
       13 
16 
     | 
    
         
             
                      parser.new do |opts|
         
     | 
| 
       14 
17 
     | 
    
         
             
                        opts.banner = "Usage: skunk [options] [paths]\n"
         
     | 
| 
         @@ -19,6 +22,10 @@ module Skunk 
     | 
|
| 
       19 
22 
     | 
    
         
             
                          self.mode = :compare_branches
         
     | 
| 
       20 
23 
     | 
    
         
             
                        end
         
     | 
| 
       21 
24 
     | 
    
         | 
| 
      
 25 
     | 
    
         
            +
                        opts.on("-o", "--out FILE", "Output report to file") do |filename|
         
     | 
| 
      
 26 
     | 
    
         
            +
                          self.output_filename = filename
         
     | 
| 
      
 27 
     | 
    
         
            +
                        end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
       22 
29 
     | 
    
         
             
                        opts.on_tail("-v", "--version", "Show gem's version") do
         
     | 
| 
       23 
30 
     | 
    
         
             
                          self.mode = :version
         
     | 
| 
       24 
31 
     | 
    
         
             
                        end
         
     | 
| 
         @@ -28,6 +35,10 @@ module Skunk 
     | 
|
| 
       28 
35 
     | 
    
         
             
                        end
         
     | 
| 
       29 
36 
     | 
    
         
             
                      end.parse!(@argv)
         
     | 
| 
       30 
37 
     | 
    
         
             
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    def to_h
         
     | 
| 
      
 40 
     | 
    
         
            +
                      super.merge(output_filename: output_filename)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
       31 
42 
     | 
    
         
             
                  end
         
     | 
| 
       32 
43 
     | 
    
         
             
                end
         
     | 
| 
       33 
44 
     | 
    
         
             
              end
         
     | 
| 
         @@ -3,34 +3,34 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            require "rubycritic/core/analysed_module"
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module RubyCritic
         
     | 
| 
       6 
     | 
    
         
            -
              # Monkey-patches RubyCritic::AnalysedModule to add a  
     | 
| 
      
 6 
     | 
    
         
            +
              # Monkey-patches RubyCritic::AnalysedModule to add a skunk_score method
         
     | 
| 
       7 
7 
     | 
    
         
             
              class AnalysedModule
         
     | 
| 
       8 
8 
     | 
    
         
             
                PERFECT_COVERAGE = 100
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                # Returns a numeric value that represents the  
     | 
| 
      
 10 
     | 
    
         
            +
                # Returns a numeric value that represents the skunk_score of a module:
         
     | 
| 
       11 
11 
     | 
    
         
             
                #
         
     | 
| 
       12 
     | 
    
         
            -
                # If module is perfectly covered,  
     | 
| 
      
 12 
     | 
    
         
            +
                # If module is perfectly covered, skunk score is the same as the
         
     | 
| 
       13 
13 
     | 
    
         
             
                # `churn_times_cost`
         
     | 
| 
       14 
14 
     | 
    
         
             
                #
         
     | 
| 
       15 
     | 
    
         
            -
                # If module has no coverage,  
     | 
| 
      
 15 
     | 
    
         
            +
                # If module has no coverage, skunk score is a penalized value of
         
     | 
| 
       16 
16 
     | 
    
         
             
                # `churn_times_cost`
         
     | 
| 
       17 
17 
     | 
    
         
             
                #
         
     | 
| 
       18 
     | 
    
         
            -
                # For now the  
     | 
| 
      
 18 
     | 
    
         
            +
                # For now the skunk_score is calculated by multiplying `churn_times_cost`
         
     | 
| 
       19 
19 
     | 
    
         
             
                # times the lack of coverage.
         
     | 
| 
       20 
20 
     | 
    
         
             
                #
         
     | 
| 
       21 
21 
     | 
    
         
             
                # For example:
         
     | 
| 
       22 
22 
     | 
    
         
             
                #
         
     | 
| 
       23 
23 
     | 
    
         
             
                # When `churn_times_cost` is 100 and module is perfectly covered:
         
     | 
| 
       24 
     | 
    
         
            -
                #  
     | 
| 
      
 24 
     | 
    
         
            +
                # skunk_score => 100
         
     | 
| 
       25 
25 
     | 
    
         
             
                #
         
     | 
| 
       26 
26 
     | 
    
         
             
                # When `churn_times_cost` is 100 and module is not covered at all:
         
     | 
| 
       27 
     | 
    
         
            -
                #  
     | 
| 
      
 27 
     | 
    
         
            +
                # skunk_score => 100 * 100 = 10_000
         
     | 
| 
       28 
28 
     | 
    
         
             
                #
         
     | 
| 
       29 
29 
     | 
    
         
             
                # When `churn_times_cost` is 100 and module is covered at 75%:
         
     | 
| 
       30 
     | 
    
         
            -
                #  
     | 
| 
      
 30 
     | 
    
         
            +
                # skunk_score => 100 * 25 (percentage uncovered) = 2_500
         
     | 
| 
       31 
31 
     | 
    
         
             
                #
         
     | 
| 
       32 
32 
     | 
    
         
             
                # @return [Float]
         
     | 
| 
       33 
     | 
    
         
            -
                def  
     | 
| 
      
 33 
     | 
    
         
            +
                def skunk_score
         
     | 
| 
       34 
34 
     | 
    
         
             
                  return cost.round(2) if coverage == PERFECT_COVERAGE
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
36 
     | 
    
         
             
                  (cost * (PERFECT_COVERAGE - coverage.to_i)).round(2)
         
     | 
| 
         @@ -43,5 +43,25 @@ module RubyCritic 
     | 
|
| 
       43 
43 
     | 
    
         
             
                  safe_churn = churn.positive? ? churn : 1
         
     | 
| 
       44 
44 
     | 
    
         
             
                  @churn_times_cost ||= (safe_churn * cost).round(2)
         
     | 
| 
       45 
45 
     | 
    
         
             
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                # Returns a hash with these attributes:
         
     | 
| 
      
 48 
     | 
    
         
            +
                #   - file
         
     | 
| 
      
 49 
     | 
    
         
            +
                #   - skunk_score
         
     | 
| 
      
 50 
     | 
    
         
            +
                #   - churn_times_cost
         
     | 
| 
      
 51 
     | 
    
         
            +
                #   - churn
         
     | 
| 
      
 52 
     | 
    
         
            +
                #   - cost
         
     | 
| 
      
 53 
     | 
    
         
            +
                #   - coverage
         
     | 
| 
      
 54 
     | 
    
         
            +
                #
         
     | 
| 
      
 55 
     | 
    
         
            +
                # @return [Hash]
         
     | 
| 
      
 56 
     | 
    
         
            +
                def to_hash
         
     | 
| 
      
 57 
     | 
    
         
            +
                  {
         
     | 
| 
      
 58 
     | 
    
         
            +
                    file: pathname.to_s,
         
     | 
| 
      
 59 
     | 
    
         
            +
                    skunk_score: skunk_score,
         
     | 
| 
      
 60 
     | 
    
         
            +
                    churn_times_cost: churn_times_cost,
         
     | 
| 
      
 61 
     | 
    
         
            +
                    churn: churn,
         
     | 
| 
      
 62 
     | 
    
         
            +
                    cost: cost.round(2),
         
     | 
| 
      
 63 
     | 
    
         
            +
                    coverage: coverage.round(2)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  }
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
       46 
66 
     | 
    
         
             
              end
         
     | 
| 
       47 
67 
     | 
    
         
             
            end
         
     |