reviewer 0.1.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.alexignore +1 -0
  3. data/.github/FUNDING.yml +3 -0
  4. data/.github/workflows/main.yml +81 -11
  5. data/.github/workflows/release.yml +98 -0
  6. data/.gitignore +1 -1
  7. data/.inch.yml +3 -1
  8. data/.reek.yml +175 -0
  9. data/.reviewer.example.yml +27 -12
  10. data/.reviewer.future.yml +221 -0
  11. data/.reviewer.yml +191 -28
  12. data/.reviewer_stdout +0 -0
  13. data/.rubocop.yml +34 -1
  14. data/CHANGELOG.md +42 -2
  15. data/Gemfile +39 -1
  16. data/Gemfile.lock +294 -72
  17. data/README.md +315 -7
  18. data/RELEASING.md +190 -0
  19. data/Rakefile +117 -0
  20. data/dependency_decisions.yml +61 -0
  21. data/exe/fmt +1 -1
  22. data/exe/rvw +1 -1
  23. data/lib/reviewer/arguments/files.rb +60 -27
  24. data/lib/reviewer/arguments/keywords.rb +39 -43
  25. data/lib/reviewer/arguments/tags.rb +21 -14
  26. data/lib/reviewer/arguments.rb +107 -29
  27. data/lib/reviewer/batch/formatter.rb +87 -0
  28. data/lib/reviewer/batch.rb +46 -35
  29. data/lib/reviewer/capabilities.rb +81 -0
  30. data/lib/reviewer/command/string/env.rb +16 -6
  31. data/lib/reviewer/command/string/flags.rb +14 -5
  32. data/lib/reviewer/command/string.rb +53 -24
  33. data/lib/reviewer/command.rb +69 -39
  34. data/lib/reviewer/configuration/loader.rb +70 -0
  35. data/lib/reviewer/configuration.rb +14 -4
  36. data/lib/reviewer/context.rb +15 -0
  37. data/lib/reviewer/doctor/config_check.rb +46 -0
  38. data/lib/reviewer/doctor/environment_check.rb +58 -0
  39. data/lib/reviewer/doctor/formatter.rb +75 -0
  40. data/lib/reviewer/doctor/keyword_check.rb +85 -0
  41. data/lib/reviewer/doctor/opportunity_check.rb +88 -0
  42. data/lib/reviewer/doctor/report.rb +63 -0
  43. data/lib/reviewer/doctor/tool_inventory.rb +41 -0
  44. data/lib/reviewer/doctor.rb +28 -0
  45. data/lib/reviewer/history.rb +36 -12
  46. data/lib/reviewer/output/formatting.rb +40 -0
  47. data/lib/reviewer/output/printer.rb +105 -0
  48. data/lib/reviewer/output.rb +54 -65
  49. data/lib/reviewer/prompt.rb +38 -0
  50. data/lib/reviewer/report/formatter.rb +124 -0
  51. data/lib/reviewer/report.rb +100 -0
  52. data/lib/reviewer/runner/failed_files.rb +66 -0
  53. data/lib/reviewer/runner/formatter.rb +103 -0
  54. data/lib/reviewer/runner/guidance.rb +79 -0
  55. data/lib/reviewer/runner/result.rb +150 -0
  56. data/lib/reviewer/runner/strategies/captured.rb +232 -0
  57. data/lib/reviewer/runner/strategies/{verbose.rb → passthrough.rb} +15 -24
  58. data/lib/reviewer/runner.rb +179 -35
  59. data/lib/reviewer/session/formatter.rb +87 -0
  60. data/lib/reviewer/session.rb +208 -0
  61. data/lib/reviewer/setup/catalog.rb +233 -0
  62. data/lib/reviewer/setup/detector.rb +61 -0
  63. data/lib/reviewer/setup/formatter.rb +94 -0
  64. data/lib/reviewer/setup/gemfile_lock.rb +55 -0
  65. data/lib/reviewer/setup/generator.rb +54 -0
  66. data/lib/reviewer/setup/tool_block.rb +112 -0
  67. data/lib/reviewer/setup.rb +41 -0
  68. data/lib/reviewer/shell/result.rb +25 -11
  69. data/lib/reviewer/shell/timer.rb +47 -27
  70. data/lib/reviewer/shell.rb +46 -21
  71. data/lib/reviewer/tool/conversions.rb +20 -0
  72. data/lib/reviewer/tool/file_resolver.rb +54 -0
  73. data/lib/reviewer/tool/settings.rb +107 -56
  74. data/lib/reviewer/tool/test_file_mapper.rb +73 -0
  75. data/lib/reviewer/tool/timing.rb +78 -0
  76. data/lib/reviewer/tool.rb +88 -47
  77. data/lib/reviewer/tools.rb +47 -33
  78. data/lib/reviewer/version.rb +1 -1
  79. data/lib/reviewer.rb +114 -54
  80. data/reviewer.gemspec +21 -20
  81. data/structure.svg +1 -0
  82. metadata +113 -148
  83. data/.ruby-version +0 -1
  84. data/lib/reviewer/command/string/verbosity.rb +0 -51
  85. data/lib/reviewer/command/verbosity.rb +0 -65
  86. data/lib/reviewer/conversions.rb +0 -27
  87. data/lib/reviewer/guidance.rb +0 -73
  88. data/lib/reviewer/keywords/git/staged.rb +0 -48
  89. data/lib/reviewer/keywords/git.rb +0 -14
  90. data/lib/reviewer/keywords.rb +0 -9
  91. data/lib/reviewer/loader.rb +0 -59
  92. data/lib/reviewer/printer.rb +0 -25
  93. data/lib/reviewer/runner/strategies/quiet.rb +0 -90
data/exe/rvw CHANGED
@@ -4,4 +4,4 @@
4
4
 
5
5
  require_relative '../lib/reviewer'
6
6
 
7
- Reviewer.review(clear_screen: true)
7
+ Reviewer.review
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open3'
4
+
3
5
  module Reviewer
4
6
  class Arguments
5
7
  # Generates a Ruby-friendly list (Array) of files to run the command against from the provided
@@ -9,36 +11,43 @@ module Reviewer
9
11
 
10
12
  alias raw provided
11
13
 
12
- # Generates and instance of files from the provided arguments
13
- # @param provided: Reviewer.arguments.files.raw [Array, String] file arguments provided
14
- # directly via the -f or --files flag on the command line.
15
- # @param keywords: Reviewer.arguments.keywords [Array, String] keywords that can potentially
16
- # be translated to a list of files (ex. 'staged')
14
+ # Generates an instance of files from the provided arguments
15
+ # @param provided [Array<String>] file arguments provided
16
+ # directly via the -f or --files flag on the command line
17
+ # @param keywords [Array<String>] reserved keywords that can potentially
18
+ # be translated to a list of files (e.g. 'staged', 'modified')
19
+ # @param output [Output] the console output handler
20
+ # @param on_git_error [Proc, nil] callback invoked with the error message
21
+ # when a git command fails (nil silently swallows the error)
22
+ #
23
+ # @example Using the `-f` flag: `rvw -f ./file.rb`
24
+ # reviewer = Reviewer::Arguments::Files.new(provided: ['./file.rb'], keywords: [])
25
+ # reviewer.to_a # => ['./file.rb']
26
+ # @example Using the `--files` flag: `rvw --files ./file.rb,./directory/file.rb
27
+ # reviewer = Reviewer::Arguments::Files.new(provided: ['./file.rb','./directory/file.rb'], keywords: [])
28
+ # reviewer.to_a # => ['./file.rb','./directory/file.rb']
17
29
  #
18
- # @return [Arguments::Files] the container for determining targeted files from the provided
19
- # command line arguments
20
- def initialize(provided: Reviewer.arguments.files.raw, keywords: Reviewer.arguments.keywords)
30
+ # @return [self]
31
+ def initialize(provided: [], keywords: [], output: Output.new, on_git_error: nil)
21
32
  @provided = Array(provided)
22
33
  @keywords = Array(keywords)
34
+ @output = output
35
+ @on_git_error = on_git_error
23
36
  end
24
37
 
25
38
  # Provides the full list of file/path values derived from the command-line arguments
26
39
  #
27
40
  # @return [Array<String>] full collection of the file arguments as a string
28
- def to_a
29
- file_list
30
- end
41
+ def to_a = file_list
31
42
 
32
43
  # Provides the full list of file/path values derived from the command-line arguments
33
44
  #
34
- # @return [String] comma-separated string of the derived tag values
35
- def to_s
36
- to_a.join(',')
37
- end
45
+ # @return [String] comma-separated string of the derived file values
46
+ def to_s = to_a.join(',')
38
47
 
39
48
  # Summary of the state of the file arguments
40
49
  #
41
- # @return [Hash] represents the summary of the file values parsed from the command-line
50
+ # @return [Hash<Symbol, Array<String>>] summarizes all of the resulting file values
42
51
  def to_h
43
52
  {
44
53
  provided: provided.sort,
@@ -50,7 +59,7 @@ module Reviewer
50
59
  private
51
60
 
52
61
  # Combines the sorted list of unique files/paths by merging the explicitly-provided file
53
- # arguments as well as those that were translated from any relevant keyword arguments.
62
+ # arguments as well as those that were translated from any relevant keyword arguments.
54
63
  #
55
64
  # @return [Array] full list of files/paths passed via command-line including those extracted
56
65
  # as a result of a keyword argument like `staged`
@@ -63,25 +72,49 @@ module Reviewer
63
72
 
64
73
  # Converts relevant keywords to the list of files they implicitly represent.
65
74
  #
66
- # @return [Array] list of files/paths translated from any keyword arguments that represent a
67
- # list of files
75
+ # @return [Array<String>] list of files/paths translated from any keyword arguments that
76
+ # represent a list of files
68
77
  def from_keywords
69
78
  return [] unless keywords.any?
70
79
 
71
80
  keywords.map do |keyword|
72
- next unless respond_to?(keyword.to_sym, true)
81
+ method_name = keyword.to_sym
82
+ next unless respond_to?(method_name, true)
73
83
 
74
- send(keyword.to_sym)
84
+ send(method_name)
75
85
  end.flatten.compact.uniq
76
86
  end
77
87
 
78
- # If `staged` is passed as a keyword via the command-line, this will get the list of staged
79
- # files via Git
80
- #
81
- # @return [Array] list of the currently staged files
82
88
  def staged
83
- # Use git for list of staged fields
84
- ::Reviewer::Keywords::Git::Staged.list
89
+ git_files(%w[diff --staged --name-only])
90
+ end
91
+
92
+ def unstaged
93
+ git_files(%w[diff --name-only])
94
+ end
95
+
96
+ def modified
97
+ git_files(%w[diff --name-only HEAD])
98
+ end
99
+
100
+ def untracked
101
+ git_files(%w[ls-files --others --exclude-standard])
102
+ end
103
+
104
+ # Executes a git command and returns the output as an array of file paths
105
+ # @param options [Array<String>] the git command options
106
+ #
107
+ # @return [Array<String>] the output lines from the command
108
+ def git_files(options)
109
+ command = (%w[git --no-pager] + options).join(' ')
110
+ stdout, stderr, status = Open3.capture3(command)
111
+
112
+ return stdout.split("\n").reject(&:empty?) if status.success?
113
+
114
+ raise SystemCallError.new("Git Error: #{stderr} (#{command})", status.exitstatus.to_i)
115
+ rescue SystemCallError => e
116
+ @on_git_error&.call(e.message)
117
+ []
85
118
  end
86
119
  end
87
120
  end
@@ -4,35 +4,41 @@ module Reviewer
4
4
  class Arguments
5
5
  # Handles interpreting all 'leftover' arguments and translating them to file-related,
6
6
  # tag-related, or tool-related arguments
7
+ #
8
+ # @!attribute provided
9
+ # @return [Array<String>] the keywords extracted from the command-line arguments
7
10
  class Keywords
8
- RESERVED = %w[staged].freeze
11
+ RESERVED = %w[staged unstaged modified untracked failed].freeze
9
12
 
10
- attr_accessor :provided
13
+ attr_reader :provided
14
+
15
+ # Sets the tools collection after initialization when tools become available
16
+ # @param value [Tools] the configured tools collection
17
+ # @return [Tools] the tools collection
18
+ attr_writer :tools
11
19
 
12
20
  alias raw provided
13
21
 
14
- # Generates an instace of parsed keywords from the provided arguments
15
- # @param *provided [Array<String>] the leftover (non-flag) arguments from the command line
22
+ # Generates an instance of parsed keywords from the provided arguments
23
+ # @param provided [Array<String>] the leftover (non-flag) arguments from the command line
24
+ # @param tools [Tools] the collection of configured tools for keyword recognition
16
25
  #
17
- # @return [Arguments::Keywords]
18
- def initialize(*provided)
26
+ # @return [self]
27
+ def initialize(*provided, tools: nil)
19
28
  @provided = Array(provided.flatten)
29
+ @tools = tools
20
30
  end
21
31
 
22
- # Proves the full list of raw keyword arguments explicitly passed via command-line as an array
32
+ # Provides the full list of raw keyword arguments explicitly passed via command-line as an array
23
33
  #
24
- # @return [Array] full collection of the provided keyword arguments as a string
25
- def to_a
26
- provided
27
- end
34
+ # @return [Array<String>] full collection of the provided keyword arguments
35
+ def to_a = provided
28
36
 
29
37
  # Provides the full list of raw keyword arguments explicitly passed via command-line as a
30
38
  # comma-separated string
31
39
  #
32
- # @return [String] comma-separated list of the file arguments as a string
33
- def to_s
34
- to_a.join(',')
35
- end
40
+ # @return [String] comma-separated list of the keyword arguments as a string
41
+ def to_s = to_a.join(',')
36
42
 
37
43
  # Summary of the state of keyword arguments based on how Reviewer parsed them
38
44
  #
@@ -50,52 +56,47 @@ module Reviewer
50
56
  end
51
57
  alias inspect to_h
52
58
 
59
+ # Whether the `failed` keyword was provided
60
+ #
61
+ # @return [Boolean] true if the `failed` keyword is present
62
+ def failed? = provided.include?('failed')
63
+
53
64
  # Extracts reserved keywords from the provided arguments
54
65
  #
55
66
  # @return [Array<String>] intersection of provided arguments and reserved keywords
56
- def reserved
57
- intersection_with RESERVED
58
- end
67
+ def reserved = intersection_with(RESERVED)
59
68
 
60
69
  # Extracts keywords that match configured tags for enabled tools
61
70
  #
62
71
  # @return [Array<String>] intersection of provided arguments and configured tags for tools
63
- def for_tags
64
- intersection_with configured_tags
65
- end
72
+ def for_tags = intersection_with(configured_tags)
66
73
 
67
74
  # Extracts keywords that match configured tool keys
68
75
  #
69
76
  # @return [Array<String>] intersection of provided arguments and configured tool names
70
- def for_tool_names
71
- intersection_with configured_tool_names
72
- end
77
+ def for_tool_names = intersection_with(configured_tool_names)
73
78
 
74
79
  # Extracts keywords that match any possible recognized keyword values
75
80
  #
76
81
  # @return [Array<String>] intersection of provided arguments and recognizable keywords
77
- def recognized
78
- intersection_with possible
79
- end
82
+ def recognized = intersection_with(possible)
80
83
 
81
84
  # Extracts keywords that don't match any possible recognized keyword values
82
85
  #
83
86
  # @return [Array<String>] leftover keywords that weren't recognized
84
- def unrecognized
85
- (provided - recognized).uniq.sort
86
- end
87
+ def unrecognized = (provided - recognized).uniq.sort
87
88
 
88
89
  # Provides the complete list of all recognized keywords based on configuration
89
90
  #
90
- # @return [Array<String>] all keywords that Reviewer can recognized
91
- def possible
92
- (RESERVED + configured_tags + configured_tool_names).uniq.sort
93
- end
91
+ # @return [Array<String>] all keywords that Reviewer can recognize
92
+ def possible = (RESERVED + configured_tags + configured_tool_names).uniq.sort
94
93
 
95
94
  # Provides the complete list of all configured tags for enabled tools
96
95
  #
97
96
  # @return [Array<String>] all unique configured tags
98
97
  def configured_tags
98
+ return [] unless tools
99
+
99
100
  tools.enabled.map(&:tags).flatten.uniq.sort
100
101
  end
101
102
 
@@ -103,6 +104,8 @@ module Reviewer
103
104
  #
104
105
  # @return [Array<String>] all unique configured tools
105
106
  def configured_tool_names
107
+ return [] unless tools
108
+
106
109
  # We explicitly don't sort the tool names list because Reviewer uses the configuration order
107
110
  # to determine the execution order. So not sorting maintains the predicted order it will run
108
111
  # in and leaves the option to sort to the consuming code if needed
@@ -111,20 +114,13 @@ module Reviewer
111
114
 
112
115
  private
113
116
 
114
- # Provides a collection of enabled Tools for convenient access
115
- #
116
- # @return [Array<Reviewer::Tool>] collection of all currently enabled tools
117
- def tools
118
- @tools ||= Reviewer.tools
119
- end
117
+ attr_reader :tools
120
118
 
121
119
  # Syntactic sugar for finding intersections with valid keywords
122
120
  # @param values [Array<String>] the collection to use for finding intersecting values
123
121
  #
124
122
  # @return [Array<String>] the list of intersecting values
125
- def intersection_with(values)
126
- (values & provided).uniq.sort
127
- end
123
+ def intersection_with(values) = (values & provided).uniq.sort
128
124
  end
129
125
  end
130
126
  end
@@ -4,19 +4,30 @@ module Reviewer
4
4
  class Arguments
5
5
  # Handles the logic of translating tag arguments
6
6
  class Tags
7
- attr_accessor :provided, :keywords
7
+ # @!attribute provided
8
+ # @return [Array<String>] tags explicitly provided via -t or --tags flag
9
+ # @!attribute keywords
10
+ # @return [Array<String>] tags derived from keyword arguments
11
+ attr_reader :provided, :keywords
8
12
 
9
13
  alias raw provided
10
14
 
11
- # Generates an instace of parsed tags from the provided arguments
12
- # @param provided: Reviewer.arguments.tags.raw [Array<String>] tag arguments provided
15
+ # Generates an instance of parsed tags from the provided arguments by merging tag arguments
16
+ # that were provided via either flags or keywords
17
+ # @param provided [Array<String>] tag arguments provided
13
18
  # directly via the -t or --tags flag on the command line.
14
- # @param keywords: Reviewer.arguments.keywords [Array, String] keywords that can potentially
19
+ # @param keywords [Array, String] keywords that can potentially
15
20
  # be translated to a list of tags based on the tags used in the configuration file
16
21
  #
17
- # @return [Arguments::Tags] the container for extracting tags from the provided command line
18
- # arguments
19
- def initialize(provided: Reviewer.arguments.tags.raw, keywords: Reviewer.arguments.keywords.for_tags)
22
+ # @example Using keywords: `rvw ruby` (assuming a 'ruby' tag is defined)
23
+ # Reviewer::Arguments::Tags.new.to_a # => ['ruby']
24
+ # @example Using the `-t` flag: `rvw -t ruby`
25
+ # Reviewer::Arguments::Tags.new.to_a # => ['ruby']
26
+ # @example Using the `--tags` flag: `rvw -t ruby,css`
27
+ # Reviewer::Arguments::Tags.new.to_a # => ['css', 'ruby']
28
+ #
29
+ # @return [self]
30
+ def initialize(provided: [], keywords: [])
20
31
  @provided = Array(provided)
21
32
  @keywords = Array(keywords)
22
33
  end
@@ -24,16 +35,12 @@ module Reviewer
24
35
  # Provides the full list of tags values derived from the command-line arguments
25
36
  #
26
37
  # @return [Array<String>] full collection of the tag arguments as a string
27
- def to_a
28
- tag_list
29
- end
38
+ def to_a = tag_list
30
39
 
31
40
  # Provides the full list of tag values derived from the command-line arguments
32
41
  #
33
42
  # @return [String] comma-separated string of the derived tag values
34
- def to_s
35
- to_a.join(',')
36
- end
43
+ def to_s = to_a.join(',')
37
44
 
38
45
  # Summary of the state of the tag arguments
39
46
  #
@@ -49,7 +56,7 @@ module Reviewer
49
56
  private
50
57
 
51
58
  # Combines the sorted list of unique tags by merging the explicitly-provided tag arguments
52
- # as well as those that were recognized from any relevant keyword arguments.
59
+ # as well as those that were recognized from any relevant keyword arguments.
53
60
  #
54
61
  # @return [Array] full list of tags passed via command-line including those matching keyword
55
62
  # arguments
@@ -19,32 +19,60 @@ module Reviewer
19
19
  # `rvw ruby staged`
20
20
  #
21
21
  class Arguments
22
- attr_accessor :options
22
+ # Valid output format options for the --format flag
23
+ KNOWN_FORMATS = %i[streaming summary json].freeze
24
+
25
+ # @!attribute options
26
+ # @return [Slop::Result] the parsed command-line options
27
+ attr_reader :options
23
28
 
24
29
  attr_reader :output
25
30
 
26
- # A catch all for aguments passed to reviewer via the command-line.
27
- # @param options = ARGV [Hash] options to parse and extract the relevant values for a run
31
+ # Parses command-line arguments and makes them available as tags, files, and keywords.
32
+ # @param options [Array<String>] the command-line arguments to parse (defaults to ARGV)
33
+ # @param output [Output] the console output handler for displaying messages
34
+ #
35
+ # @example Using all options: `rvw keyword_one keyword_two --files ./example.rb,./example_test.rb --tags syntax`
36
+ # reviewer = Reviewer::Arguments.new
37
+ # reviewer.files.to_a # => ['./example.rb','./example_test.rb']
38
+ # reviewer.tags.to_a # => ['syntax']
39
+ # reviewer.keywords.to_a # => ['keyword_one', 'keyword_two']
28
40
  #
29
- # @return [Reviewer::Arguments] the full collection of arguments provided via the command line
30
- def initialize(options = ARGV)
31
- @output = Output.new
32
- @options = Slop.parse options do |opts|
33
- opts.array '-f', '--files', 'a list of comma-separated files or paths', delimiter: ',', default: []
34
- opts.array '-t', '--tags', 'a list of comma-separated tags', delimiter: ',', default: []
35
-
36
- opts.on '-v', '--version', 'print the version' do
37
- @output.info VERSION
38
- exit
39
- end
40
-
41
- opts.on '-h', '--help', 'print the help' do
42
- @output.info opts
43
- exit
44
- end
45
- end
41
+ # @return [self]
42
+ def initialize(options = ARGV, output: Output.new)
43
+ @output = output
44
+ @options = Slop.parse(options) { |opts| configure_options(opts) }
45
+ end
46
+
47
+ private
48
+
49
+ def configure_options(opts)
50
+ configure_input_options(opts)
51
+ configure_output_options(opts)
52
+ configure_info_options(opts)
53
+ end
54
+
55
+ def configure_input_options(opts)
56
+ opts.array '-f', '--files', 'a list of comma-separated files or paths', delimiter: ',', default: []
57
+ opts.array '-t', '--tags', 'a list of comma-separated tags', delimiter: ',', default: []
58
+ end
59
+
60
+ def configure_output_options(opts)
61
+ opts.on '-r', '--raw', 'force raw output (no capturing)'
62
+ opts.on '-j', '--json', 'output results as JSON'
63
+ opts.string '--format', 'output format (streaming, summary, json)', default: 'streaming'
64
+ end
65
+
66
+ def configure_info_options(opts)
67
+ opts.on('-v', '--version', 'print the version')
68
+ opts.on('-h', '--help', 'print the help')
69
+ opts.on('-c', '--capabilities', 'output capabilities as JSON')
46
70
  end
47
71
 
72
+ def session_formatter = @session_formatter ||= Session::Formatter.new(output)
73
+
74
+ public
75
+
48
76
  # Converts the arguments to a hash for versatility
49
77
  #
50
78
  # @return [Hash] The files, tags, and keywords collected from the command line options
@@ -59,23 +87,73 @@ module Reviewer
59
87
 
60
88
  # The tag arguments collected from the command line via the `-t` or `--tags` flag
61
89
  #
62
- # @return [Arguments::Tags] an colelction of the tag arguments collected from the command-line
63
- def tags
64
- @tags ||= Arguments::Tags.new(provided: options[:tags])
65
- end
90
+ # @return [Arguments::Tags] a collection of the tag arguments collected from the command-line
91
+ def tags = @tags ||= Arguments::Tags.new(provided: options[:tags])
66
92
 
67
93
  # The file arguments collected from the command line via the `-f` or `--files` flag
68
94
  #
69
- # @return [Arguments::Files] an collection of the file arguments collected from the command-line
95
+ # @return [Arguments::Files] a collection of the file arguments collected from the command-line
70
96
  def files
71
- @files ||= Arguments::Files.new(provided: options[:files])
97
+ @files ||= Arguments::Files.new(
98
+ provided: options[:files],
99
+ keywords: keywords.reserved,
100
+ output: output,
101
+ on_git_error: session_formatter.method(:git_error)
102
+ )
72
103
  end
73
104
 
74
105
  # The leftover arguments collected from the command line without being associated with a flag
75
106
  #
76
- # @return [Arguments::Keywords] an collection of the leftover arguments as keywords
77
- def keywords
78
- @keywords ||= Arguments::Keywords.new(options.arguments)
107
+ # @return [Arguments::Keywords] a collection of the leftover arguments as keywords
108
+ def keywords = @keywords ||= Arguments::Keywords.new(options.arguments)
109
+
110
+ # Whether the --help flag was passed
111
+ #
112
+ # @return [Boolean] true if help was requested
113
+ def help? = options[:help]
114
+
115
+ # Whether the --version flag was passed
116
+ #
117
+ # @return [Boolean] true if version was requested
118
+ def version? = options[:version]
119
+
120
+ # Whether to force raw/passthrough output regardless of tool count
121
+ #
122
+ # @return [Boolean] true if raw output mode is requested
123
+ def raw? = options[:raw]
124
+
125
+ # Whether to output results as JSON
126
+ #
127
+ # @return [Boolean] true if JSON output mode is requested
128
+ def json? = options[:json]
129
+
130
+ # The output format for results
131
+ #
132
+ # @return [Symbol] the output format (:streaming, :summary, or :json)
133
+ def format
134
+ return :json if json?
135
+
136
+ value = options[:format].to_sym
137
+ return value if KNOWN_FORMATS.include?(value)
138
+
139
+ session_formatter.invalid_format(options[:format], KNOWN_FORMATS)
140
+ :streaming
141
+ end
142
+
143
+ # Whether output should be streamed directly (not captured for later formatting)
144
+ #
145
+ # @return [Boolean] true if in streaming mode
146
+ def streaming? = format == :streaming
147
+
148
+ # Determines the appropriate runner strategy based on CLI flags
149
+ #
150
+ # @param multiple_tools [Boolean] whether multiple tools are being run
151
+ # @return [Class] the strategy class (Captured or Passthrough)
152
+ def runner_strategy(multiple_tools:)
153
+ return Runner::Strategies::Passthrough if raw?
154
+ return Runner::Strategies::Captured unless streaming?
155
+
156
+ multiple_tools ? Runner::Strategies::Captured : Runner::Strategies::Passthrough
79
157
  end
80
158
  end
81
159
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../output/formatting'
4
+
5
+ module Reviewer
6
+ class Batch
7
+ # Display logic for batch execution: summary, run preview, missing tools
8
+ class Formatter
9
+ include Output::Formatting
10
+
11
+ attr_reader :output, :printer
12
+ private :output, :printer
13
+
14
+ # Creates a formatter for batch execution display
15
+ # @param output [Output] the console output handler
16
+ #
17
+ # @return [Formatter]
18
+ def initialize(output)
19
+ @output = output
20
+ @printer = output.printer
21
+ end
22
+
23
+ # Displays a one-line success summary with timing and tool count
24
+ # @param tool_count [Integer] the number of tools that ran
25
+ # @param seconds [Float] total elapsed time in seconds
26
+ #
27
+ # @return [void]
28
+ def summary(tool_count, seconds)
29
+ output.newline
30
+ printer.print(:success, CHECKMARK)
31
+ printer.print(:muted, " ~#{seconds.round(1)} seconds")
32
+ printer.print(:muted, " for #{tool_count} tools") if tool_count > 1
33
+ output.newline
34
+ end
35
+
36
+ # Displays a preview of which tools will run and their target files
37
+ # @param entries [Array<Hash>] each with :name and :files keys
38
+ #
39
+ # @return [void]
40
+ def run_summary(entries)
41
+ return if entries.empty?
42
+
43
+ entries.each { |entry| print_run_entry(entry) }
44
+ output.newline
45
+ end
46
+
47
+ # Displays a list of tools whose executables were not found, with install hints
48
+ # @param missing [Array<Runner::Result>] the results for missing tools
49
+ # @param tools [Array<Tool>] the tools that were in the batch
50
+ #
51
+ # @return [void]
52
+ def missing_tools(missing, tools:)
53
+ output.newline
54
+ printer.puts(:warning, "#{missing.size} not installed:")
55
+ tool_lookup = tools.to_h { |tool| [tool.key, tool] }
56
+ missing.each { |result| print_missing_hint(result.tool_name, tool_lookup[result.tool_key]) }
57
+ output.newline
58
+ end
59
+
60
+ # Displays a message when `rvw failed` is used but no tools failed in the last run
61
+ #
62
+ # @return [void]
63
+ def no_failures_to_retry
64
+ printer.puts(:muted, 'No failures to retry')
65
+ end
66
+
67
+ # Displays a message when `rvw failed` is used but no previous run exists in history
68
+ #
69
+ # @return [void]
70
+ def no_previous_run
71
+ printer.puts(:muted, 'No previous run found')
72
+ end
73
+
74
+ private
75
+
76
+ def print_missing_hint(name, tool)
77
+ hint = tool&.installable? ? tool.install_command : ''
78
+ printer.puts(:muted, " #{name.ljust(22)}#{hint}")
79
+ end
80
+
81
+ def print_run_entry(entry)
82
+ printer.puts(:muted, entry[:name])
83
+ entry[:files].each { |file| printer.puts(:muted, " #{file}") }
84
+ end
85
+ end
86
+ end
87
+ end