lib/fix/command.rb in fix-command-0.4.0 vs lib/fix/command.rb in fix-command-0.5.0

- old
+ new

@@ -4,60 +4,52 @@ # Namespace for the Fix framework. # # @api public # -# @example Let's test a duck's spec! -# Fix::Command.run('duck_fix.rb', '--warnings') +# @example Let's test a duck! +# Fix::Command.run('./duck/', '--warnings') # module Fix - # Fix reads command line configuration options from files in two different - # locations: - # - # Local: "./.fix" (i.e. in the project's root directory) - # Global: "~/.fix" (i.e. in the user's home directory) - # - # Options declared in the local file override those in the global file, while - # those declared in command-line will override any ".fix" file. - COMMAND_LINE_OPTIONS_FILE = '.fix' - # Open the command class. # # @api public # - # @example Let's test a duck's spec! - # Command.run('duck_fix.rb', '--warnings') + # @example Let's test a duck! + # Command.run('./duck/', '--warnings') # class Command # Handle the parameters passed to fix command from the console. # # @api public # # @example Let's test a duck's spec! - # run('duck_fix.rb', '--warnings') + # run('./duck/', '--warnings') # # @param args [Array] A list of files and options. # # @raise [SystemExit] The result of the tests. def self.run(*args) - config = process_args(args) + config = process_args(true, args) + if args.size > 1 + fail ArgumentError, "wrong number of directories (given #{args.size})" + end + file_paths = fetch_file_paths( + config.fetch(:diff), config.fetch(:prefix), - config.fetch(:suffix), *args - ).to_a.shuffle(random: Random.new(config.fetch(:random))) + config.fetch(:suffix), + (args.first || '.') + ).to_a.sort.shuffle(random: Random.new(config.fetch(:random))) - str = "\e[37m" - str += '> fix ' - str += file_paths.join(' ') - str += ' --debug' if $DEBUG - str += ' --warnings' if $VERBOSE - str += ' --random' if config.fetch(:random) - str += ' --prefix' if config.fetch(:prefix) - str += ' --suffix' if config.fetch(:suffix) + puts report(config, *file_paths) - puts "#{str} \e[22m" + if file_paths.empty? + puts 'Specs not found.' + exit + end status = true file_paths.each do |file_path| begin @@ -71,95 +63,150 @@ exit(status) end # @private - def self.process_args(args) + # + # @return [String] Report the command-line. + def self.report(config, *file_paths) + str = '> fix ' + file_paths.join(' ') + str += ' --debug' if $DEBUG + str += ' --warnings' if $VERBOSE + str += ' --diff' if config.fetch(:diff) + str += " --random #{config.fetch(:random)}" + str += " --prefix #{config.fetch(:prefix).inspect}" + str += " --suffix #{config.fetch(:suffix).inspect}" + + ["\e[37m", str, "\e[0m"].join + end + + # @private + # + # @param load_file [Boolean] Preload configuration options from files. + # @param args [Array] List of parameters. + def self.process_args(load_file, args) options = { - debug: false, - warnings: false, - random: Random.new_seed, - prefix: '', - suffix: '_fix' + debug: false, + warnings: false, + diff: false, + random: Random.new_seed, + prefix: '', + suffix: '_fix' } opt_parser = OptionParser.new do |opts| - opts.banner = 'Usage: fix <files or directories> [options]' + opts.banner = 'Usage: fix <directory> [options]' opts.separator '' opts.separator 'Specific options:' - opts.on('--debug', 'Enable ruby debug') do - options[:debug] = $DEBUG = true + opts.on('--[no-]debug', 'Enable ruby debug') do |b| + options[:debug] = $DEBUG = b end - opts.on('--warnings', 'Enable ruby warnings') do - options[:warnings] = $VERBOSE = true + opts.on('--[no-]warnings', 'Enable ruby warnings') do |b| + options[:warnings] = $VERBOSE = b end - opts.on('--random=[SEED]', Integer, 'Predictable randomization') do |i| - options[:random] = i + opts.on('--[no-]diff', 'Regression test selection') do |b| + options[:diff] = b end - opts.on('--prefix=[PREFIX]', String, 'Prefix of the spec files') do |s| - options[:prefix] = s + opts.on('--random [SEED]', Integer, 'Predictable randomization') do |i| + options[:random] = Integer(i) end - opts.on('--suffix=[SUFFIX]', String, 'Suffix of the spec files') do |s| - options[:suffix] = s + opts.on('--prefix [PREFIX]', String, 'Prefix of the spec files') do |s| + options[:prefix] = s.to_s end + opts.on('--suffix [SUFFIX]', String, 'Suffix of the spec files') do |s| + options[:suffix] = s.to_s + end + opts.separator '' opts.separator 'Common options:' - opts.on_tail '--help', 'Show this message' do + opts.on_tail '-h', '--help', 'Show this message' do puts opts exit end - opts.on_tail '--version', 'Show the version' do + opts.on_tail '-v', '--version', 'Show the version' do puts File.read(File.join(File.expand_path(File.dirname(__FILE__)), '..', '..', 'VERSION.semver')).chomp exit end end - %w(~ .).each do |c| - config_path = File.join(File.expand_path(c), COMMAND_LINE_OPTIONS_FILE) - opt_parser.load(config_path) + if load_file + config_paths.each { |config_path| opt_parser.load(config_path) } end opt_parser.parse!(args) options end + # @return [String] The configuration file. + def self.config_file + '.fix' + end + + # @return [Array] Different locations of command-line configuration options. + def self.config_paths + %w(~ .).map { |char| File.join(File.expand_path(char), config_file) } + end + # @private # # @return [Set] A list of absolute paths. - def self.fetch_file_paths(file_prefix, file_suffix, *args) - absolute_paths = Set.new + def self.fetch_file_paths(diff, prefix, suffix, path) + unless File.directory?(path) + warn 'Sorry, invalid directory.' + exit false + end - args << '.' if args.empty? - args.each do |s| - s = File.absolute_path(s) unless s.start_with?(File::SEPARATOR) + path = File.absolute_path(path) unless path.start_with?(File::SEPARATOR) - if File.directory?(s) - spec_files = File.join(s, '**', "#{file_prefix}*#{file_suffix}.rb") - Dir.glob(spec_files).each { |spec_f| absolute_paths.add(spec_f) } - else - absolute_paths.add(s) + spec_paths = Dir.glob(File.join(path, '**', "#{prefix}*#{suffix}.rb")).to_set + return spec_paths unless diff + + Dir.chdir(path) do + modified_paths = git_diff_paths + + # select only modified specs + modified_spec_paths = spec_paths & modified_paths + + # select only unmodified specs + unmodified_spec_paths = spec_paths - modified_spec_paths + + # select only modified code + modified_code_paths = modified_paths - spec_paths + modified_code_filenames = modified_code_paths.map do |p| + p.split(File::SEPARATOR).last.gsub(/\.rb\z/, '') + end.to_set + + # select only specs of the modified code + additional_spec_paths = unmodified_spec_paths.select do |unmodified_spec_path| + unmodified_spec_filename = unmodified_spec_path.split(File::SEPARATOR).last.gsub(/\.rb\z/, '') + modified_code_filenames.any? do |modified_code_filename| + unmodified_spec_filename.include?(modified_code_filename) + end end - end - if absolute_paths.empty? - warn 'Sorry, files or directories not found.' - exit false + modified_spec_paths + additional_spec_paths end + end - absolute_paths + # @return [Set] The list of modified files. + def self.git_diff_paths + `git status --porcelain` + .split("\n") + .map { |p| File.absolute_path(p.split.last) } + .to_set end end end