1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'optparse'
require 'test_file_finder'
# Show mappings for `test_file_finder` defined in `tests.yml`.
#
# Usage:
# tooling/bin/tff_mappings --help
# tooling/bin/tff_mappings # Show summary for all commited files
# tooling/bin/tff_mappings app/ lib/ # Show summary for specific folders
# tooling/bin/tff_mappings --missing app/ # List missing files in app/ folder
# tooling/bin/tff_mappings --ignored --no-summary # List ignored files without summary
module Tooling
class TffMappings
TESTS_YAML = File.expand_path('../../tests.yml', __dir__)
STRATEGY = TestFileFinder::MappingStrategies::PatternMatching.load(TESTS_YAML)
IGNORE = %w[
**/.*ignore
**/.{,git}keep
**/*.{jpg,js,mjs,md,patch,png,sh,svg,toml,txt}
.rubocop.yml .rubocop_todo/
db/schema_migrations db/*.sql
gems/ vendor/
locale/
public/ app/assets/ ee/app/assets/
qa/
spec/components/previews/ ee/spec/components/previews/
spec/contracts/ ee/spec/contracts/
spec/factories/ ee/spec/factories/
spec/fixtures/ ee/spec/fixtures/
spec/frontend/ ee/spec/frontend/
spec/frontend_integration/ ee/spec/frontend_integration/
spec/support/ ee/spec/support/
tmp/
workhorse/
].freeze
UNIGNORE = %w[
spec/contracts/provider_specs ee/spec/contracts/provider_specs
].freeze
def self.options(argv)
options = {
found: false,
missing: false,
ignored: false,
summary: true
}
OptionParser.new do |parser|
parser.banner = "Usage: #{__FILE__} [options] [<file> ...]"
parser.on('--found', 'Show found tff mappings.')
parser.on('--missing', 'Show missing tff mappings.')
parser.on('--ignored', 'Show ignored files.')
parser.on('--all', 'Show all states: found, missing, and ignored.') do
options.except(:summary).each_key { |key| options[key] = true }
end
parser.on('--[no-]summary', 'Show summary of found and missing entries. Default: true')
parser.on('-h', '--help', 'Show this help.') do
puts parser
exit
end
end.parse!(argv, into: options)
if argv.empty?
files = `git ls-files -z`.split("\0")
else
pattern = argv
.map { |arg| File.directory?(arg) ? "#{arg.delete_suffix('/')}/**/*" : arg }
.join(",")
files = Dir.glob("{#{pattern}}").reject { |file| File.directory?(file) }
end
[options, files]
end
def initialize(options)
@options = options
@output = any_combination?(@options.except(:summary)) ? with_label : without_label
@ignore = matchers_for(IGNORE)
@unignore = matchers_for(UNIGNORE)
end
def run(files)
stats = {
total: 0,
found: 0,
missing: 0,
ignored: 0
}
files.each do |file|
stats[:total] += 1
if match_list?(@ignore, file) && !match_list?(@unignore, file)
stats[:ignored] += 1
puts @output.call('IGNORED', file) if @options[:ignored]
next
end
result = test_files(file)
if result.empty?
stats[:missing] += 1
puts @output.call('MISSING', file) if @options[:missing]
else
stats[:found] += 1
puts @output.call('FOUND', file) if @options[:found]
end
end
print_summary(stats)
end
private
def print_summary(stats)
return unless @options[:summary]
puts stats.map { |key, value| "#{key}=#{value}" }.join(' ').prepend('# ')
end
def matchers_for(list)
list.map do |item|
%r{[*?\[\{]}.match?(item) ? pattern_match(item) : start_with_match(item)
end
end
def pattern_match(glob)
->(path) { File.fnmatch?(glob, path, File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB) }
end
def start_with_match(string)
->(path) { path.start_with?(string) }
end
def match_list?(list, file)
list.any? { |matcher| matcher.call(file) }
end
def any_combination?(options)
options.values.count(&:itself) >= 2
end
def with_label
->(label, file) { "#{file} #{label}" }
end
def without_label
->(_label, file) { file }
end
def test_files(source)
tff = TestFileFinder::FileFinder.new(paths: [source])
tff.use STRATEGY
tff.test_files
end
end
end
options, files = Tooling::TffMappings.options(ARGV)
Tooling::TffMappings.new(**options).run(files)
|