diff --git a/.hound.yml b/.hound.yml new file mode 100644 index 0000000..7bae4d0 --- /dev/null +++ b/.hound.yml @@ -0,0 +1,10 @@ +# Hound bundles a RuboCop older than 0.78. That version cannot parse +# .rubocop.yml: it expects the Metrics/LineLength namespace instead of +# Layout/LineLength, and it rejects TargetRubyVersion 3.1. Pinning it to +# 2019 rules would judge this codebase, and its rbs-inline annotations, +# by cops that predate them. +# +# The CI lint job runs RuboCop 1.89 against .rubocop.yml on every pull +# request. That job is the gate for Ruby style. +ruby: + enabled: false diff --git a/lib/classifier/keywords/cli.rb b/lib/classifier/keywords/cli.rb index 878dfcf..28303fd 100644 --- a/lib/classifier/keywords/cli.rb +++ b/lib/classifier/keywords/cli.rb @@ -144,12 +144,7 @@ def command_fit if @args.empty? [@stdin ? StringIO.new(@stdin.to_s) : $stdin] else - @args.flat_map do |arg| - matches = Dir.glob(arg) - raise UsageError, "No files matched #{arg.inspect}" if matches.empty? - - matches.map { |f| File.expand_path(f) } - end.uniq + collect_files end tfidf = TFIDF.new( @@ -165,6 +160,19 @@ def command_fit @output << "Saved to #{@options[:model].inspect}" unless @options[:quiet] end + def collect_files + files = @args.flat_map do |arg| + matches = Dir.glob(arg) + raise UsageError, "No files matched #{arg.inspect}" if matches.empty? + + matches.select { |f| File.file?(f) }.map { |f| File.expand_path(f) } + end.uniq + + raise UsageError, 'No files to fit' if files.empty? + + files + end + def command_extract @args.shift @@ -174,6 +182,7 @@ def command_extract else file = File.expand_path(@args.first) raise UsageError, "File #{file.inspect} does not exist" unless File.exist?(file) + raise UsageError, "#{file.inspect} is a directory, not a file" if File.directory?(file) File.read(file) end diff --git a/test/keywords/cli_test.rb b/test/keywords/cli_test.rb index 628abaa..e5264cc 100644 --- a/test/keywords/cli_test.rb +++ b/test/keywords/cli_test.rb @@ -212,6 +212,49 @@ def test_fit_command_with_no_documents refute_path_exists @model_path end + def test_fit_command_skips_directories_matched_by_a_glob + make_articles + FileUtils.mkdir_p(File.join(@tmpdir, 'articles', 'nested')) + + result = run_cli('-m', @model_path, 'fit', *Dir.glob(File.join(@tmpdir, 'articles/*'))) + + assert_equal 0, result[:exit_code] + assert_predicate File.size(@model_path), :positive? + end + + def test_fit_command_with_directory_argument + dir = File.join(@tmpdir, 'articles') + FileUtils.mkdir_p(dir) + + result = run_cli('-m', @model_path, 'fit', dir) + + assert_equal 2, result[:exit_code] + assert_match('Error: No files to fit', result[:error]) + refute_path_exists @model_path + end + + def test_fit_command_with_not_exists_input_file + make_articles + missing = File.join(@tmpdir, 'articles', 'not_exists.txt') + + result = run_cli('-m', @model_path, 'fit', File.join(@tmpdir, 'articles/a1.txt'), missing) + + assert_equal 2, result[:exit_code] + assert_match("Error: No files matched #{missing.inspect}", result[:error]) + refute_path_exists @model_path + end + + def test_extract_command_with_directory_input + make_model + dir = File.join(@tmpdir, 'articles') + FileUtils.mkdir_p(dir) + + result = run_cli('-m', @model_path, 'extract', dir) + + assert_equal 2, result[:exit_code] + assert_match("Error: #{dir.inspect} is a directory, not a file", result[:error]) + end + def test_extract_command_with_not_exists_input_file make_model file = File.join(@tmpdir, 'not_exists.txt')