|
| 1 | +#!/usr/bin/env ruby |
| 2 | +# frozen_string_literal: true |
| 3 | + |
| 4 | +require 'colorize' |
| 5 | +require 'terminal-table' |
| 6 | + |
| 7 | +module RSpec |
| 8 | + module Mock |
| 9 | + module MigrationAnalytics |
| 10 | + class Cli |
| 11 | + class << self |
| 12 | + def call |
| 13 | + if ::ARGV.empty? |
| 14 | + print_usage |
| 15 | + exit 1 |
| 16 | + end |
| 17 | + |
| 18 | + begin |
| 19 | + verify_path(::ARGV[0]) |
| 20 | + rescue => error |
| 21 | + puts("\n❌ Error: #{error.message}".red) |
| 22 | + puts(error.backtrace) if ENV['DEBUG'] |
| 23 | + end |
| 24 | + end |
| 25 | + |
| 26 | + def verify_path(path) |
| 27 | + case |
| 28 | + when ::File.directory?(path) then verify_directory(path) |
| 29 | + else verify_file(path) |
| 30 | + end |
| 31 | + end |
| 32 | + |
| 33 | + private |
| 34 | + |
| 35 | + def print_usage |
| 36 | + puts('Usage: ruby cli.rb <path_to_spec_file_or_directory>'.yellow) |
| 37 | + puts("\nExamples:".blue) |
| 38 | + puts(' ruby cli.rb spec/models/user_spec.rb') |
| 39 | + puts(' ruby cli.rb spec/models/') |
| 40 | + puts(' ruby cli.rb spec/') |
| 41 | + end |
| 42 | + |
| 43 | + def verify_directory(dir_path) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength |
| 44 | + results = [] |
| 45 | + stats = { |
| 46 | + total_files: 0, |
| 47 | + files_with_mocks: 0, |
| 48 | + total_flexmock_occurrences: 0, |
| 49 | + total_rspec_mock_occurrences: 0, |
| 50 | + files_with_mixed_usage: 0 |
| 51 | + } |
| 52 | + |
| 53 | + ::Dir.glob("#{dir_path}/**/*_spec.rb").each do |file| |
| 54 | + stats[:total_files] += 1 |
| 55 | + result = RSpec::Mock::MigrationAnalytics::FileAnalyzer.call(file) |
| 56 | + |
| 57 | + next unless result[:has_mocks] |
| 58 | + stats[:files_with_mocks] += 1 |
| 59 | + stats[:total_flexmock_occurrences] += result[:flexmock_count] |
| 60 | + stats[:total_rspec_mock_occurrences] += result[:rspec_mock_count] |
| 61 | + stats[:files_with_mixed_usage] += 1 if result[:has_mixed_usage] |
| 62 | + results << result |
| 63 | + end |
| 64 | + |
| 65 | + print_summary(results, stats) |
| 66 | + end |
| 67 | + |
| 68 | + def verify_file(file_path) |
| 69 | + return puts("File not found: #{file_path}".red) unless ::File.exist?(file_path) |
| 70 | + return puts("Not a Ruby spec file: #{file_path}".yellow) unless file_path.end_with?('_spec.rb') |
| 71 | + |
| 72 | + print_file_result(RSpec::Mock::MigrationAnalytics::FileAnalyzer.call(file_path)) |
| 73 | + end |
| 74 | + |
| 75 | + def print_file_result(result) |
| 76 | + puts("\n=== Mock Usage Analysis: #{result[:file_path]} ===".blue) |
| 77 | + |
| 78 | + if result[:has_mocks] |
| 79 | + print_mock_statistics(result) |
| 80 | + print_locations_table('Flexmock Usage', result[:flexmock_locations]) if result[:flexmock_locations].any? |
| 81 | + print_locations_table('RSpec Mock Usage', result[:rspec_mock_locations]) if result[:rspec_mock_locations].any? |
| 82 | + else |
| 83 | + puts('✅ No mocking usage found'.green) |
| 84 | + end |
| 85 | + end |
| 86 | + |
| 87 | + def print_summary(results, stats) |
| 88 | + puts("\n=== Migration Status Report ===".blue) |
| 89 | + |
| 90 | + total_mocks = stats[:total_flexmock_occurrences] + stats[:total_rspec_mock_occurrences] |
| 91 | + migration_progress = |
| 92 | + total_mocks.zero? ? 100 : (stats[:total_rspec_mock_occurrences].to_f / total_mocks * 100).round(2) |
| 93 | + |
| 94 | + print_summary_table(stats, migration_progress) |
| 95 | + print_files_table(results) if results.any? |
| 96 | + end |
| 97 | + |
| 98 | + def print_mock_statistics(result) |
| 99 | + total_mocks = result[:flexmock_count] + result[:rspec_mock_count] |
| 100 | + migration_progress = (result[:rspec_mock_count].to_f / total_mocks * 100).round(2) |
| 101 | + puts( |
| 102 | + Terminal::Table.new do |t| |
| 103 | + t.add_row(['Total Mocks', total_mocks]) |
| 104 | + t.add_row(['Flexmock Usage', result[:flexmock_count]]) |
| 105 | + t.add_row(['RSpec Mock Usage', result[:rspec_mock_count]]) |
| 106 | + t.add_row(['Migration Progress', "#{migration_progress}%"]) |
| 107 | + end |
| 108 | + ) |
| 109 | + end |
| 110 | + |
| 111 | + def print_locations_table(title, locations) |
| 112 | + return if locations.empty? |
| 113 | + |
| 114 | + puts("\n#{title}:".yellow) |
| 115 | + puts( |
| 116 | + Terminal::Table.new do |table| |
| 117 | + table.headings = %w[Line Type Content] |
| 118 | + locations.each do |loc| |
| 119 | + table.add_row(create_location_row(loc)) |
| 120 | + end |
| 121 | + end |
| 122 | + ) |
| 123 | + end |
| 124 | + |
| 125 | + def create_location_row(loc) |
| 126 | + type_str = loc[:type].nil? ? 'unknown' : loc[:type] |
| 127 | + color = determine_color(loc[:type]) |
| 128 | + |
| 129 | + [ |
| 130 | + loc[:line_number].to_s.yellow, |
| 131 | + type_str.respond_to?(color) ? type_str.send(color) : type_str, |
| 132 | + loc[:content] |
| 133 | + ] |
| 134 | + end |
| 135 | + |
| 136 | + def determine_color(type) |
| 137 | + case type |
| 138 | + when 'migration mock block' then :cyan |
| 139 | + when 'expect mock', 'allow mock' then :blue |
| 140 | + when 'verifying double' then :green |
| 141 | + else :light_white |
| 142 | + end |
| 143 | + end |
| 144 | + |
| 145 | + def print_summary_table(stats, migration_progress) |
| 146 | + puts( |
| 147 | + Terminal::Table.new do |table| |
| 148 | + table.add_row(['Total Spec Files', stats[:total_files]]) |
| 149 | + table.add_row(['Files with Mocks', stats[:files_with_mocks]]) |
| 150 | + table.add_row(['Files with Mixed Usage', stats[:files_with_mixed_usage]]) |
| 151 | + table.add_row(['Total Flexmock Occurrences', stats[:total_flexmock_occurrences]]) |
| 152 | + table.add_row(['Total RSpec Mock Occurrences', stats[:total_rspec_mock_occurrences]]) |
| 153 | + table.add_row(['Migration Progress', "#{migration_progress}%"]) |
| 154 | + end |
| 155 | + ) |
| 156 | + end |
| 157 | + |
| 158 | + def print_files_table(results) |
| 159 | + puts("\n=== Files Requiring Migration ===".red) |
| 160 | + puts( |
| 161 | + Terminal::Table.new do |table| |
| 162 | + table.headings = ['File Path', 'Flexmock Count', 'RSpec Mock Count', 'Progress'] |
| 163 | + results.sort_by { |row| -row[:flexmock_count] }.each do |result| |
| 164 | + table.add_row(create_file_row(result)) |
| 165 | + end |
| 166 | + end |
| 167 | + ) |
| 168 | + end |
| 169 | + |
| 170 | + def create_file_row(result) |
| 171 | + total = result[:flexmock_count] + result[:rspec_mock_count] |
| 172 | + progress = |
| 173 | + total.zero? ? 100 : (result[:rspec_mock_count].to_f / total * 100).round(2) |
| 174 | + [ |
| 175 | + result[:file_path], |
| 176 | + result[:flexmock_count], |
| 177 | + result[:rspec_mock_count], |
| 178 | + "#{progress}%" |
| 179 | + ] |
| 180 | + end |
| 181 | + end |
| 182 | + end |
| 183 | + end |
| 184 | + end |
| 185 | +end |
0 commit comments