Class: Countless::Statistics

Inherits:
Object
  • Object
show all
Defined in:
lib/countless/statistics.rb

Overview

The source code statistics displaying handler.

Heavily stolen from: bit.ly/3qpvgfu

Defined Under Namespace

Classes: Calculator

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*dirs) ⇒ Countless::Statistics

Initialize a new source code statistics displaying handler. When no configurations are passed in directly, we fallback to the configured statistics directories of the gem.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/countless/statistics.rb', line 17

def initialize(*dirs)
  base_path = Countless.configuration.base_path

  # Resolve the given directory configurations to actual files
  dirs = dirs.presence || Countless.statistic_directories
  @dirs = dirs.each_with_object([]) do |cur, memo|
    copy = cur.deep_dup
    copy[:files] = Array(copy[:files])

    if copy[:pattern].is_a? Regexp
      copy[:files] += Dir[
        File.join(copy[:dir] || base_path, '**/*')
      ].select { |path| File.file?(path) && copy[:pattern].match?(path) }
    else
      copy[:files] += Dir[copy[:pattern]]
    end

    copy[:files].uniq!
    memo << copy if copy[:files].present?
  end

  @statistics = calculate_statistics
  @total = calculate_total if @dirs.length > 1
end

Instance Attribute Details

#dirsObject (readonly)

Make the extracted information accessible



9
10
11
# File 'lib/countless/statistics.rb', line 9

def dirs
  @dirs
end

#statisticsObject (readonly)

Make the extracted information accessible



9
10
11
# File 'lib/countless/statistics.rb', line 9

def statistics
  @statistics
end

#totalObject (readonly)

Make the extracted information accessible



9
10
11
# File 'lib/countless/statistics.rb', line 9

def total
  @total
end

Instance Method Details

#calculate_codeInteger

Calculate the total lines of code.



90
91
92
93
# File 'lib/countless/statistics.rb', line 90

def calculate_code
  @statistics.values.reject { |conf| conf[:test] }
                    .map { |conf| conf[:stats].code_lines }.sum
end

#calculate_file_statistics(name, files) ⇒ Countless::Statistics::Calculator

Setup a new Calculator for the given directory/pattern in order to extract the individual file statistics and calculate the sub-totals.

We match the pattern against the individual file name and the relative file path. This allows top-level only matches.



79
80
81
82
83
84
85
# File 'lib/countless/statistics.rb', line 79

def calculate_file_statistics(name, files)
  Calculator.new(name: name).tap do |calc|
    Cloc.stats(*files).each do |path, stats|
      calc.add_by_file_path(path, **stats)
    end
  end
end

#calculate_statisticsHash{String => Hash{Symbol => Mixed}}

Calculate all statistics for the configured directories and pass back a named hash.



58
59
60
61
62
63
64
65
66
# File 'lib/countless/statistics.rb', line 58

def calculate_statistics
  @dirs.to_h do |conf|
    [
      conf[:name],
      conf.merge(stats: calculate_file_statistics(conf[:name],
                                                  conf[:files]))
    ]
  end
end

#calculate_testsInteger

Calculate the total lines of testing code.



98
99
100
101
# File 'lib/countless/statistics.rb', line 98

def calculate_tests
  @statistics.values.select { |conf| conf[:test] }
                    .map { |conf| conf[:stats].code_lines }.sum
end

#calculate_totalCountless::Statistics::Calculator

Calculate the total statistics of all sub-statistics for the configured directories.



46
47
48
49
50
51
# File 'lib/countless/statistics.rb', line 46

def calculate_total
  calculator = Calculator.new(name: 'Total')
  @statistics.values.each_with_object(calculator) do |conf, total|
    total.add(conf[:stats])
  end
end

#code_test_stats_lineString

Return the final meta statistics line.



167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/countless/statistics.rb', line 167

def code_test_stats_line
  code  = calculate_code
  tests = calculate_tests
  ratio = tests.fdiv(code)
  ratio = '0' if ratio.nan?

  res = [
    "Code LOC: #{code}",
    "Test LOC: #{tests}",
    "Code to Test Ratio: 1:#{format('%.1f', ratio)}"
  ].join(' ' * 5)
  "  #{res}"
end

#to_sString

Convert the code statistics to a formatted string buffer.

rubocop:disable Metrics/MethodLength – because of the complex formatting

logic with fully dynamic columns widths


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/countless/statistics.rb', line 109

def to_s
  col_sizes = {}
  rows = to_table.map do |row|
    next row unless row.is_a?(Array)

    row = row.map(&:to_s)
    cols = row.map(&:length).each_with_index.map { |len, idx| [idx, [len]] }
    col_sizes.deep_merge!(cols.to_h) { |_, left, right| left + right }
    row
  end

  # Calculate the correct column sizes
  col_sizes = col_sizes.values.each_with_object([]) do |widths, memo|
    memo << (widths.max + 2)
  end

  # Enforce the correct column sizes per row
  splitter = ([0] + col_sizes + [0]).map { |size| '-' * size }.join('+')
  rows.each_with_object([]) do |row, memo|
    next memo << splitter if row == :splitter
    next memo << row if row.is_a?(String)

    cols = row.each_with_index.map do |col, idx|
      meth = idx.zero? ? :ljust : :rjust
      col.send(meth, col_sizes[idx] - 2)
    end
    memo << "| #{cols.join(' | ')} |"
  end.join("\n")
end

#to_tableArray<Array<String, Integer>, Symbol>

Convert the code statistics to a processable table structure. Each element in the resulting array is a single line, while array elements reflect columns. The special :splitter row value will be converted later by #to_s.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/countless/statistics.rb', line 146

def to_table
  table = [
    :splitter,
    %w[Name Lines LOC Comments Classes Methods M/C LOC/M],
    :splitter
  ]
  @statistics.each_value { |conf| table << conf[:stats].to_h.values }
  table << :splitter

  if @total
    table << @total.to_h.values
    table << :splitter
  end

  table << code_test_stats_line
  table
end