reviewer 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64e0b63ddf470414a26b5903508646ace97b1b0ee6e2b0244e7233d9e6717652
4
- data.tar.gz: 97b9d1d998959319791022b37f00fde7b6424411c728e98fb8109c352078088d
3
+ metadata.gz: 538f085294cbfbb5ce7c8412a39991534a2251b83978894133537f20e2bbf1ba
4
+ data.tar.gz: 39bee00497eccb91256b772f5af8a77746374648efd884c9ffb719f9fd7b1e16
5
5
  SHA512:
6
- metadata.gz: da483ff2104b138d463c1688731c3eb83ecb8778105af38efbe98f6c7597f9aa4a069341580a3fc7bb34833cf83119d64967a56398e0cea4f97ae7bebc2ae5f2
7
- data.tar.gz: f9f1106bf11209abd1a5af2d3ad151413a1026cbe43a4a5db171414d335df7cae9c7ab8d922e661da0625240163d55906eccf9b8fe03a245869f97b00bc94ce8
6
+ metadata.gz: 872fbfb03db5c520c6b20d0eedf624ae37c9c9c2452b34bb286a4f9c52eddafe2846f5e401d702424e14dae9a153cefd6e3f8e8bf15a9a3e4c58e76940af64e9
7
+ data.tar.gz: 4bc2ae679b15339577014e404fb8340c436e0c7ae26f6f46ac2baaf94d14592c42610a343e6eba679928bdcb3d1c0698f0e75d68771f78fa4f1bef9f03ed8dfd
data/.inch.yml ADDED
@@ -0,0 +1,4 @@
1
+ files:
2
+ # define files included in the analysis (defaults to ["{app,lib}/**/*.rb"])
3
+ included:
4
+ - lib/**/*.rb
@@ -14,14 +14,16 @@
14
14
  # prepare: // Optional. Command to run prior to the review phase. ex. 'bundle exec bundle-audit update'
15
15
  # review: // Required. The only truly required field because this is the whole point.
16
16
  # format: // Optional. Command to auto-update rule violations when possible.
17
- # quiet_option: // Optional, but strongly suggested. Helps keep output under control when running multiple tools.
17
+ # quiet_option: // Optional, but strongly suggested. Helps keep output under control when running multiple tools.
18
18
  # max_exit_status: // Optional, defaults to 0. Some tools like Yarn Audit essentially won't return less than a 3. This specifies the threshold that's still considered passing.
19
- # env: // Optional. A way to specify necessary environment variables for the tools commands. The key is the variable name, and the value is, well, the value.
20
- # example_one: value // - The names will automatically be capitalized, so you can freely use lower-case here.
21
- # example_one: value // - Reviewer is smart enough to handle string values with spaces and automatically quote them.
22
- # flags: // Optional. A way to specify flags *only for the review command*. The key is the flag name, and the value is, well, the value.
23
- # example_one: value // - Reviewer is smart enough to handle single-letter (-f) and multi-letter (--format) flags.
24
- # example_two: value // - It's highly-recommended to use the longer-name format for flags when possible to serve as self-documentation.
19
+ # files_flag: // Optional, defaults to '' (empty string). The name of the flag used to pass subsets of files to the command.
20
+ # files_separator: // Optional, defaults to ' ' (single space). The character used to separate lists of files and directories.
21
+ # env: // Optional. A way to specify necessary environment variables for the tools commands. The key is the variable name, and the value is, well, the value.
22
+ # example_one: value // - The names will automatically be capitalized, so you can freely use lower-case here.
23
+ # example_one: value // - Reviewer is smart enough to handle string values with spaces and automatically quote them.
24
+ # flags: // Optional. A way to specify flags *only for the review command*. The key is the flag name, and the value is, well, the value.
25
+ # example_one: value // - Reviewer is smart enough to handle single-letter (-f) and multi-letter (--format) flags.
26
+ # example_two: value // - It's highly-recommended to use the longer-name format for flags when possible to serve as self-documentation.
25
27
 
26
28
  # This is an example for a YAML block for a command-line tool:
27
29
  #
data/.reviewer.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  bundler_audit:
2
- tags: [critical, dependencies, ruby]
2
+ tags: [critical, dependencies, ruby, dev]
3
3
  name: Bundler Audit
4
- description: Audit Gem Dependencies for Security Issues
4
+ description: Review Gem Dependencies for Security Issues
5
5
  links:
6
6
  home: https://github.com/rubysec/bundler-audit
7
7
  install: https://github.com/rubysec/bundler-audit#install
@@ -14,7 +14,7 @@ bundler_audit:
14
14
  tests:
15
15
  name: Minitest
16
16
  description: Unit Tests
17
- tags: [ruby, tests]
17
+ tags: [ruby, tests, dev]
18
18
  links:
19
19
  home:
20
20
  commands:
@@ -22,8 +22,9 @@ tests:
22
22
  quiet_option: '--silent'
23
23
 
24
24
  rubocop:
25
+ name: Rubocop
26
+ description: Review Ruby Syntax & Formatting for Consistency
25
27
  tags: [ruby, syntax]
26
- description: Review Ruby syntax/formatting for consistency
27
28
  links:
28
29
  home: https://rubocop.org
29
30
  install: https://docs.rubocop.org/rubocop/1.13/installation.html
@@ -34,3 +35,16 @@ rubocop:
34
35
  review: 'bundle exec rubocop --parallel'
35
36
  format: 'bundle exec rubocop --auto-correct'
36
37
  quiet_option: '--format q'
38
+ files_flag: ''
39
+ files_list_separator: ' '
40
+
41
+ inch:
42
+ disabled: true # Inch provides guidance and is generally run solo
43
+ tags: [docs, ruby, dev]
44
+ name: Inch
45
+ description: Review Ruby Documentation
46
+ links:
47
+ home: https://rrrene.org/inch/
48
+ commands:
49
+ install: 'bundle exec gem install inch'
50
+ review: 'bundle exec inch'
data/CHANGELOG.md CHANGED
@@ -1,8 +1,29 @@
1
1
  ## [Unreleased]
2
2
 
3
- - TODO: Add Runner to handle timing, logging, and exit status
4
- - TODO: Add Targets to handle targeting specific files
5
- - TODO: Add support for Targets in Tool/Command generator
3
+ - TODO: Improve and streamline installation
4
+ - TODO: Add support for targeting specific files
5
+
6
+ ## [0.1.3] - 2021-07-07
7
+
8
+ The most significant update to how the core commands work and how the command-line arguments are handled. Most of the overall structure is starting to feel stable enough to begin documenting and adding comments.
9
+
10
+ - Commands are now `rvw` and `fmt`
11
+ - Adds command-line arguments support
12
+ - Adds support for specifying tags via the command-line
13
+ - Adds support for specifying files via the command-line
14
+ - Adds support for handling keywords via the command-line
15
+ - Improved configuration and loading
16
+ - Adds Tools class for more convenient filtering of tools based on arguments
17
+ - Begins the process of adding documentation comments
18
+
19
+ ## [0.1.2] - 2021-05-04
20
+
21
+ The bare minimum works now, but it's not quite there for day-to-day use. It works well enough that it's being used to review this project, but there's much more to do.
22
+
23
+ - Add Runner to wrap individual commands
24
+ - Add benchmark/timing to Runner
25
+ - Extract Logging to a dedicated class
26
+ - Intelligently handle non-zero exit statuses
6
27
 
7
28
  ## [0.1.1] - 2021-04-17
8
29
 
data/Gemfile CHANGED
@@ -7,4 +7,5 @@ gemspec
7
7
 
8
8
  gem 'rake', '~> 13.0'
9
9
 
10
- gem 'minitest', '~> 5.0'
10
+ gem 'minitest', '~> 5.14.4'
11
+ gem 'minitest-color', '~> 0.0.2'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reviewer (0.1.2)
4
+ reviewer (0.1.3)
5
5
  activesupport
6
6
  colorize
7
7
  slop
@@ -19,14 +19,26 @@ GEM
19
19
  bundler-audit (0.8.0)
20
20
  bundler (>= 1.2.0, < 3)
21
21
  thor (~> 1.0)
22
+ coderay (1.1.3)
22
23
  colorize (0.8.1)
23
24
  concurrent-ruby (1.1.8)
24
25
  i18n (1.8.10)
25
26
  concurrent-ruby (~> 1.0)
27
+ inch (0.8.0)
28
+ pry
29
+ sparkr (>= 0.2.0)
30
+ term-ansicolor
31
+ yard (~> 0.9.12)
32
+ method_source (1.0.0)
26
33
  minitest (5.14.4)
34
+ minitest-color (0.0.2)
35
+ minitest (~> 5)
27
36
  parallel (1.20.1)
28
37
  parser (3.0.1.0)
29
38
  ast (~> 2.4.1)
39
+ pry (0.14.1)
40
+ coderay (~> 1.1)
41
+ method_source (~> 1.0)
30
42
  rainbow (3.0.0)
31
43
  rake (13.0.3)
32
44
  regexp_parser (2.1.1)
@@ -48,10 +60,17 @@ GEM
48
60
  rubocop
49
61
  ruby-progressbar (1.11.0)
50
62
  slop (4.8.2)
63
+ sparkr (0.4.1)
64
+ sync (0.5.0)
65
+ term-ansicolor (1.7.1)
66
+ tins (~> 1.0)
51
67
  thor (1.1.0)
68
+ tins (1.28.0)
69
+ sync
52
70
  tzinfo (2.0.4)
53
71
  concurrent-ruby (~> 1.0)
54
72
  unicode-display_width (2.0.0)
73
+ yard (0.9.26)
55
74
  zeitwerk (2.4.2)
56
75
 
57
76
  PLATFORMS
@@ -61,7 +80,9 @@ PLATFORMS
61
80
 
62
81
  DEPENDENCIES
63
82
  bundler-audit
64
- minitest (~> 5.0)
83
+ inch
84
+ minitest (~> 5.14.4)
85
+ minitest-color (~> 0.0.2)
65
86
  rake (~> 13.0)
66
87
  reviewer!
67
88
  rubocop
data/README.md CHANGED
@@ -17,10 +17,10 @@ bundle exec rake notes
17
17
 
18
18
  You run...
19
19
  ```
20
- ./bin/rvw
20
+ rvw
21
21
  ```
22
22
 
23
- Having a simpler command is just the beginning through. It also cleans up the output and lets you run subsets of commands.
23
+ But that's just the beginning. It also cleans up the output and lets easily you run subsets of commands for different contexts.
24
24
 
25
25
  For more detailed information, take a look at the [Overview](https://github.com/garrettdimon/reviewer/wiki/Overview) and [Usage](https://github.com/garrettdimon/reviewer/wiki/Usage) pages in the wiki.
26
26
 
data/{bin → exe}/fmt RENAMED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # frozen_string_literal: true
4
+
3
5
  require_relative '../lib/reviewer'
4
6
 
5
7
  Reviewer.format
data/{bin → exe}/rvw RENAMED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # frozen_string_literal: true
4
+
3
5
  require_relative '../lib/reviewer'
4
6
 
5
7
  Reviewer.review
data/lib/reviewer.rb CHANGED
@@ -3,8 +3,8 @@
3
3
  require 'active_support/core_ext/string'
4
4
  require 'benchmark'
5
5
 
6
- require_relative 'reviewer/configuration'
7
6
  require_relative 'reviewer/arguments'
7
+ require_relative 'reviewer/configuration'
8
8
  require_relative 'reviewer/loader'
9
9
  require_relative 'reviewer/logger'
10
10
  require_relative 'reviewer/runner'
@@ -17,38 +17,91 @@ module Reviewer
17
17
  class Error < StandardError; end
18
18
 
19
19
  class << self
20
- attr_writer :configuration, :logger
21
- end
20
+ attr_writer :configuration
22
21
 
23
- def self.review
24
- elapsed_time = Benchmark.realtime do
25
- Tools.all.each do |tool|
26
- next if tool.disabled?
22
+ # Runs the `review` command for the specified tools/files. Reviewer expects all configured
23
+ # commands that are not disabled to have an entry for the `review` command.
24
+ #
25
+ # @return [void] Prints output to the console
26
+ def review
27
+ perform(:review)
28
+ end
27
29
 
28
- exit_status = Runner.new(tool, :review).run
30
+ # Runs the `format` command for the specified tools/files for which it is configured.
31
+ #
32
+ # @return [void] Prints output to the console
33
+ def format
34
+ perform(:format)
35
+ end
29
36
 
30
- break unless exit_status <= tool.max_exit_status
31
- end
37
+ # The collection of arguments that were passed via the command line.
38
+ #
39
+ # @return [Reviewer::Arguments] exposes tags, files, and keywords from arguments
40
+ def arguments
41
+ @arguments ||= Arguments.new
32
42
  end
33
- puts "\n➤ Total Time: #{elapsed_time.round(3)}s\n"
34
- end
35
43
 
36
- def self.format
37
- # options = Arguments.new
38
- Tools.all.each do |tool|
39
- Runner.run(tool, :format)
44
+ # An interface for the collection of configured tools for accessing subsets of tools
45
+ # based on enabled/disabled, tags, keywords, etc.
46
+ #
47
+ # @return [Reviewer::Tools] exposes the set of tools to be run in a given context
48
+ def tools
49
+ @tools ||= Tools.new
40
50
  end
41
- end
42
51
 
43
- def self.configuration
44
- @configuration ||= Configuration.new
45
- end
52
+ # The primary output method for Reviewer to consistently display success/failure details for a
53
+ # unique run of each tool and the collective summary when relevant.
54
+ #
55
+ # @return [Reviewer::Logger] prints formatted output to the command line.
56
+ def logger
57
+ @logger ||= Logger.new
58
+ end
46
59
 
47
- def self.reset
48
- @configuration = Configuration.new
49
- end
60
+ # Exposes the configuration options for Reviewer.
61
+ #
62
+ # @return [Reviewer::Configuration] configuration settings instance
63
+ def configuration
64
+ @configuration ||= Configuration.new
65
+ end
66
+
67
+ # A block approach to configuring Reviewer.
68
+ #
69
+ # @example Set configuration file path
70
+ # Reviewer.configure do |config|
71
+ # config.file = '~/configuration.yml'
72
+ # end
73
+ #
74
+ # @return [Reviewer::Configuration] Reviewer configuration settings
75
+ def configure
76
+ yield(configuration)
77
+ end
78
+
79
+ private
50
80
 
51
- def self.configure
52
- yield(configuration)
81
+ # Provides a consistent approach to running and benchmarking commmands and preventing further
82
+ # execution of later tools if a command fails.
83
+ # @param command_type [Symbol] the specific command to run for each tool
84
+ #
85
+ # @example Run the `review` command for each relevant tool
86
+ # perform(:review)
87
+ #
88
+ # @return [Hash] the exit status (in integer format) for each command run
89
+ def perform(command_type)
90
+ results = {}
91
+
92
+ elapsed_time = Benchmark.realtime do
93
+ tools.current.each do |tool|
94
+ runner = Runner.new(tool, command_type, logger: logger)
95
+ exit_status = runner.run
96
+ results[tool.key] = exit_status
97
+
98
+ # If a single tool fails, stop there.
99
+ break unless exit_status <= tool.max_exit_status
100
+ end
101
+ end
102
+ logger.total_time(elapsed_time)
103
+
104
+ results
105
+ end
53
106
  end
54
107
  end
@@ -1,9 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'arguments/keywords'
4
+ require_relative 'arguments/files'
5
+ require_relative 'arguments/tags'
6
+
3
7
  require 'slop'
4
8
 
5
9
  module Reviewer
6
- # Handles option parsing for bin/review
10
+ # Handles option parsing for `rvw` and `fmt` commands
11
+ #
12
+ # @example
13
+ #
14
+ # `rvw`
15
+ # `rvw -t ruby`
16
+ # `rvw -f ./example.rb,./example_test.rb`
17
+ # `rvw staged`
18
+ # `rvw --files ./example.rb,./example_test.rb --tags syntax`
19
+ # `rvw ruby staged`
20
+ #
7
21
  class Arguments
8
22
  attr_accessor :options
9
23
 
@@ -24,21 +38,28 @@ module Reviewer
24
38
  end
25
39
  end
26
40
 
27
- def files
28
- options[:files]
41
+ def inspect
42
+ {
43
+ files: files.raw,
44
+ tags: tags.raw,
45
+ keywords: keywords.raw
46
+ }
29
47
  end
30
48
 
31
49
  def tags
32
- options[:tags]
50
+ @tags ||= Arguments::Tags.new(provided: options[:tags])
33
51
  end
34
52
 
35
- def arguments
36
- options.arguments
53
+ def files
54
+ @files ||= Arguments::Files.new(provided: options[:files])
37
55
  end
38
56
 
39
57
  def keywords
40
- # TODO: Filter arguments to only for allowed keywords to be defined later.
41
- arguments
58
+ @keywords ||= Arguments::Keywords.new(options.arguments)
59
+ end
60
+
61
+ def tool_names
62
+ @tool_names ||= keywords.for_tool_names.to_a
42
63
  end
43
64
  end
44
65
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewer
4
+ class Arguments
5
+ # Generates the list of files to run the command against
6
+ class Files
7
+ attr_reader :provided, :keywords
8
+
9
+ alias raw provided
10
+
11
+ def initialize(provided: Reviewer.arguments.files.raw, keywords: Reviewer.arguments.keywords)
12
+ @provided = Array(provided)
13
+ @keywords = Array(keywords)
14
+ end
15
+
16
+ def to_a
17
+ file_list
18
+ end
19
+
20
+ def to_s
21
+ to_a.join(',')
22
+ end
23
+
24
+ def inspect
25
+ {
26
+ provided: provided,
27
+ from_keywords: from_keywords
28
+ }
29
+ end
30
+
31
+ private
32
+
33
+ def file_list
34
+ @file_list ||= [
35
+ *provided,
36
+ *from_keywords
37
+ ].compact.sort.uniq
38
+ end
39
+
40
+ def from_keywords
41
+ return [] unless keywords.any?
42
+
43
+ keywords.map do |keyword|
44
+ next unless respond_to?(keyword.to_sym, true)
45
+
46
+ send(keyword.to_sym)
47
+ end.flatten.uniq
48
+ end
49
+
50
+ def staged
51
+ # Use git for list of staged fields
52
+ ::Reviewer::Arguments::Keywords::Git::Staged.list
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'keywords/git'
4
+
5
+ module Reviewer
6
+ class Arguments
7
+ # Handles interpreting all 'leftover' arguments and translating them to file-related,
8
+ # tag-related, or tool-related arguments
9
+ class Keywords
10
+ RESERVED = %w[staged].freeze
11
+
12
+ attr_accessor :provided
13
+
14
+ alias raw provided
15
+
16
+ def initialize(*provided)
17
+ @provided = Array(provided.flatten)
18
+ end
19
+
20
+ def to_a
21
+ provided
22
+ end
23
+
24
+ def to_s
25
+ to_a.join(',')
26
+ end
27
+
28
+ def inspect
29
+ {
30
+ provided: provided,
31
+ recognized: recognized,
32
+ unrecognized: unrecognized,
33
+ reserved: reserved,
34
+ for_tags: for_tags,
35
+ for_tool_names: for_tool_names
36
+ }
37
+ end
38
+
39
+ # Extracts reserved keywords from the provided arguments
40
+ #
41
+ # @return [Array<String>] intersection of provided arguments and reserved keywords
42
+ def reserved
43
+ intersection_with RESERVED
44
+ end
45
+
46
+ # Extracts keywords that match configured tags for enabled tools
47
+ #
48
+ # @return [Array<String>] intersection of provided arguments and configured tags for tools
49
+ def for_tags
50
+ intersection_with configured_tags
51
+ end
52
+
53
+ # Extracts keywords that match configured tool keys
54
+ #
55
+ # @return [Array<String>] intersection of provided arguments and configured tool names
56
+ def for_tool_names
57
+ intersection_with configured_tool_names
58
+ end
59
+
60
+ # Extracts keywords that match any possible recognized keyword values
61
+ #
62
+ # @return [Array<String>] intersection of provided arguments and recognizable keywords
63
+ def recognized
64
+ intersection_with possible
65
+ end
66
+
67
+ # Extracts keywords that don't match any possible recognized keyword values
68
+ #
69
+ # @return [Array<String>] leftover keywords that weren't recognized
70
+ def unrecognized
71
+ (provided - recognized).uniq.sort
72
+ end
73
+
74
+ # Provides the complete list of all recognized keywords based on configuration
75
+ #
76
+ # @return [Array<String>] all keywords that Reviewer can recognized
77
+ def possible
78
+ (RESERVED + configured_tags + configured_tool_names).uniq.sort
79
+ end
80
+
81
+ # Provides the complete list of all configured tags for enabled tools
82
+ #
83
+ # @return [Array<String>] all unique configured tags
84
+ def configured_tags
85
+ enabled_tools.map(&:tags).flatten.uniq.sort
86
+ end
87
+
88
+ # Provides the complete list of all configured tool names for enabled tools
89
+ #
90
+ # @return [Array<String>] all unique configured and enabled tools
91
+ def configured_tool_names
92
+ # We explicitly don't sort the tool names list because Reviewer uses the configuration order
93
+ # to determine the execution order. So not sorting maintains the predicted order it will run
94
+ # in and leaves the option to sort to the consuming code if needed
95
+ enabled_tools.map { |tool| tool.key.to_s }.flatten.uniq
96
+ end
97
+
98
+ private
99
+
100
+ # Provides a collection of enabled Tools for convenient access
101
+ #
102
+ # @return [Array<Reviewer::Tool>] collection of all currently enabled tools
103
+ def enabled_tools
104
+ @enabled_tools ||= Reviewer.tools.enabled
105
+ end
106
+
107
+ # Syntactic sugar for finding intersections with valid keywords
108
+ # @param values [Array<String>] the collection to use for finding intersecting values
109
+ #
110
+ # @return [Array<String>] the list of intersecting values
111
+ def intersection_with(values)
112
+ values.intersection(provided).uniq.sort
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'git/staged'
4
+
5
+ module Reviewer
6
+ class Arguments
7
+ class Keywords
8
+ module Git
9
+ BASE_COMMAND = [
10
+ 'git',
11
+ '--no-pager'
12
+ ].freeze
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewer
4
+ class Arguments
5
+ class Keywords
6
+ module Git
7
+ # Provides a convenient interface to get the list of staged files
8
+ class Staged
9
+ OPTIONS = [
10
+ 'diff',
11
+ '--staged',
12
+ '--name-only'
13
+ ].freeze
14
+
15
+ attr_reader :stdout, :stderr, :status, :exit_status
16
+
17
+ def to_a
18
+ stdout.present? ? stdout.split("\n") : []
19
+ end
20
+
21
+ def to_s
22
+ stdout.present? ? stdout : ''
23
+ end
24
+
25
+ def inspect
26
+ {
27
+ command: command,
28
+ stdout: stdout,
29
+ stderr: stderr,
30
+ status: status,
31
+ results: results
32
+ }
33
+ end
34
+
35
+ def list
36
+ @stdout, @stderr, @status = Open3.capture3(command)
37
+ @exit_status = @status.exitstatus.to_i
38
+
39
+ @status.success? ? to_a : raise_command_line_error
40
+ end
41
+
42
+ def self.list
43
+ new.list
44
+ end
45
+
46
+ def command
47
+ command_parts.join(' ')
48
+ end
49
+
50
+ private
51
+
52
+ def raise_command_line_error
53
+ message = "Git Error: #{stderr} (#{command})"
54
+ raise SystemCallError.new(message, exit_status)
55
+ end
56
+
57
+ def command_parts
58
+ BASE_COMMAND + OPTIONS
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reviewer
4
+ class Arguments
5
+ # Handles the logic of translating tag arguments
6
+ class Tags
7
+ attr_accessor :provided, :keywords
8
+
9
+ alias raw provided
10
+
11
+ def initialize(provided: Reviewer.arguments.tags.raw, keywords: Reviewer.arguments.keywords.for_tags)
12
+ @provided = Array(provided)
13
+ @keywords = Array(keywords)
14
+ end
15
+
16
+ def to_a
17
+ tag_list
18
+ end
19
+
20
+ def to_s
21
+ to_a.join(',')
22
+ end
23
+
24
+ def inspect
25
+ {
26
+ provided: provided,
27
+ from_keywords: keywords
28
+ }
29
+ end
30
+
31
+ private
32
+
33
+ def tag_list
34
+ @tag_list ||= [
35
+ *provided,
36
+ *keywords
37
+ ].compact.sort.uniq
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,19 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reviewer
4
- # Configuration for Reviewer
4
+ # Configuration values container for Reviewer
5
5
  class Configuration
6
- DEFAULT_CONFIGURATION_PATH = Dir.pwd.freeze
7
- DEFAULT_CONFIGURATION_FILE = '.reviewer.yml'
6
+ DEFAULT_PATH = Dir.pwd.freeze
7
+ DEFAULT_FILE_NAME = '.reviewer.yml'
8
+ DEFAULT_FILE = "#{DEFAULT_PATH}/#{DEFAULT_FILE_NAME}"
8
9
 
9
10
  attr_accessor :file
10
11
 
11
12
  def initialize
12
- @file = "#{DEFAULT_CONFIGURATION_PATH}/#{DEFAULT_CONFIGURATION_FILE}"
13
- end
14
-
15
- def tools
16
- @tools ||= Loader.new.to_h
13
+ @file = DEFAULT_FILE
17
14
  end
18
15
  end
19
16
  end
@@ -10,24 +10,51 @@ module Reviewer
10
10
 
11
11
  class InvalidConfigurationError < StandardError; end
12
12
 
13
- attr_reader :configuration
13
+ class MissingReviewCommandError < StandardError; end
14
14
 
15
- def initialize
15
+ attr_reader :configuration, :file
16
+
17
+ def initialize(file = Reviewer.configuration.file)
18
+ @file = file
16
19
  @configuration = HashWithIndifferentAccess.new(configuration_hash)
20
+
21
+ validate_configuration!
17
22
  end
18
23
 
19
24
  def to_h
20
25
  configuration
21
26
  end
22
27
 
28
+ def self.configuration
29
+ new.configuration
30
+ end
31
+
23
32
  private
24
33
 
34
+ def validate_configuration!
35
+ # Any additional guidance for configuration issues will live here
36
+ require_review_commands!
37
+ end
38
+
39
+ def require_review_commands!
40
+ configuration.each do |key, value|
41
+ commands = value[:commands]
42
+
43
+ next if commands.key?(:review)
44
+
45
+ # Ideally, folks would want to fill out everything to receive the most benefit,
46
+ # but realistically, the 'review' command is the only required value. If the key
47
+ # is missing, or maybe there was a typo, fail right away.
48
+ raise MissingReviewCommandError, "'#{key}' does not have a 'review' key under 'commands' in `#{file}`"
49
+ end
50
+ end
51
+
25
52
  def configuration_hash
26
- @configuration_hash ||= YAML.load_file(Reviewer.configuration.file)
53
+ @configuration_hash ||= YAML.load_file(@file)
27
54
  rescue Errno::ENOENT
28
- raise MissingConfigurationError, "Tools configuration file couldn't be found - #{Reviewer.configuration.file}"
55
+ raise MissingConfigurationError, "Tools configuration file couldn't be found at `#{file}`"
29
56
  rescue Psych::SyntaxError => e
30
- raise InvalidConfigurationError, "Tools configuration file has a syntax error - #{e.message}"
57
+ raise InvalidConfigurationError, "Tools configuration file (#{file}) has a syntax error: #{e.message}"
31
58
  end
32
59
  end
33
60
  end
@@ -5,6 +5,13 @@ require 'colorize'
5
5
  module Reviewer
6
6
  # Clean formatter for logging to $stdout
7
7
  class StandardOutFormatter < ::Logger::Formatter
8
+ # Overrides ::Logger::Formatter `call` to more present output more concisely
9
+ # @param _severity [Logger::Severity] Unused - Logger severity for etnry
10
+ # @param _time [DateTime] Unused - Timestamp for entry
11
+ # @param _progname [String] Unused - Name of the current program for entry
12
+ # @param message [String] The string to print to $stdout
13
+ #
14
+ # @return [type] [description]
8
15
  def call(_severity, _time, _progname, message)
9
16
  "#{message}\n"
10
17
  end
@@ -26,11 +33,13 @@ module Reviewer
26
33
  end
27
34
 
28
35
  def command(cmd)
29
- info "#{PROMPT} #{cmd}".light_black
36
+ info "\nReviewer ran this command:"
37
+ info cmd.to_s.light_black
30
38
  end
31
39
 
32
- def rerunning(tool)
33
- info "\n\nRe-running #{tool.name} verbosely:"
40
+ def rerunning(tool, cmd)
41
+ info "\nRe-running #{tool.name} verbosely:"
42
+ info cmd.to_s.light_black
34
43
  end
35
44
 
36
45
  def success(elapsed_time)
@@ -38,12 +47,16 @@ module Reviewer
38
47
  end
39
48
 
40
49
  def failure(message)
41
- info "#{FAILURE} #{message}".red.bold
50
+ error "#{FAILURE} #{message}".red.bold
51
+ end
52
+
53
+ def total_time(elapsed_time)
54
+ info "\n➤ Total Time: #{elapsed_time.round(3)}s\n"
42
55
  end
43
56
 
44
57
  def guidance(summary, details)
45
- info " #{summary}" if summary
46
- info " #{details}".light_black if details
58
+ info "\n#{summary}" if summary
59
+ info details.to_s.light_black if details
47
60
  end
48
61
  end
49
62
  end
@@ -3,13 +3,19 @@
3
3
  require 'open3'
4
4
 
5
5
  module Reviewer
6
- # Handles running, benchmarking, and printing output for a command
6
+ # Handles running, benchmarking, and printing output for a single command
7
7
  class Runner
8
- COMMAND_NOT_FOUND_EXIT_STATUS_CODE = 127
8
+ EXECUTABLE_NOT_FOUND_EXIT_STATUS_CODE = 127
9
9
 
10
10
  attr_accessor :tool, :command
11
11
 
12
- attr_reader :elapsed_time, :stdout, :stderr, :status, :exit_status, :logger
12
+ attr_reader :elapsed_time,
13
+ :last_command_run,
14
+ :stdout,
15
+ :stderr,
16
+ :status,
17
+ :exit_status,
18
+ :logger
13
19
 
14
20
  def initialize(tool, command, logger: Logger.new)
15
21
  @tool = tool
@@ -21,8 +27,7 @@ module Reviewer
21
27
  logger.running(tool)
22
28
 
23
29
  @elapsed_time = Benchmark.realtime do
24
- prepare
25
- review
30
+ tool.format_command? ? run_format : run_review
26
31
  end
27
32
 
28
33
  print_result
@@ -34,32 +39,26 @@ module Reviewer
34
39
  def shell_out(cmd)
35
40
  @stdout, @stderr, @status = Open3.capture3(cmd)
36
41
  @exit_status = status.exitstatus
37
-
38
- logger.command(cmd) unless status.success?
42
+ @last_command_run = cmd
39
43
  end
40
44
 
41
- def prepare
45
+ def run_review
42
46
  shell_out(tool.preparation_command) if tool.prepare_command?
43
- end
44
-
45
- def review
46
47
  shell_out(tool.review_command(seed: seed))
47
48
  end
48
49
 
49
- def format
50
+ def run_format
50
51
  shell_out(tool.format_command) if tool.format_command?
51
52
  end
52
53
 
53
54
  def review_verbosely
54
55
  cmd = tool.review_command(:no_silence, seed: seed)
55
- logger.rerunning(tool)
56
- logger.command(cmd)
56
+ logger.rerunning(tool, cmd)
57
57
  system(cmd)
58
58
  end
59
59
 
60
60
  def print_result
61
61
  if status.success?
62
- # Outputs success details
63
62
  logger.success(elapsed_time)
64
63
  else
65
64
  recovery_guidance
@@ -68,6 +67,7 @@ module Reviewer
68
67
 
69
68
  def recovery_guidance
70
69
  logger.failure(error_message)
70
+ logger.command(last_command_run)
71
71
  if missing_executable?
72
72
  missing_executable_guidance
73
73
  else
@@ -84,12 +84,12 @@ module Reviewer
84
84
  end
85
85
 
86
86
  def missing_executable_guidance
87
- logger.guidance('Installation Command:', tool.installation_command) if tool.install_command?
88
- logger.guidance('Installation Help:', tool.settings.links[:install]) if tool.install_link?
87
+ logger.guidance('Try installing the tool:', tool.installation_command) if tool.install_command?
88
+ logger.guidance('Read the installation guidance:', tool.settings.links[:install]) if tool.install_link?
89
89
  end
90
90
 
91
91
  def missing_executable?
92
- (@exit_status == COMMAND_NOT_FOUND_EXIT_STATUS_CODE) ||
92
+ (@exit_status == EXECUTABLE_NOT_FOUND_EXIT_STATUS_CODE) ||
93
93
  stderr.include?("can't find executable")
94
94
  end
95
95
 
data/lib/reviewer/tool.rb CHANGED
@@ -13,6 +13,8 @@ module Reviewer
13
13
 
14
14
  delegate :name,
15
15
  :description,
16
+ :tags,
17
+ :key,
16
18
  :enabled?,
17
19
  :disabled?,
18
20
  :max_exit_status,
@@ -30,6 +32,14 @@ module Reviewer
30
32
  name
31
33
  end
32
34
 
35
+ def to_sym
36
+ key
37
+ end
38
+
39
+ def ==(other)
40
+ settings == other.settings
41
+ end
42
+
33
43
  def installation_command(verbosity_level = :no_silence)
34
44
  command_string(:install, verbosity_level: verbosity_level)
35
45
  end
@@ -4,18 +4,18 @@ module Reviewer
4
4
  class Tool
5
5
  # Converts/casts tool configuration values and provides default values if not set
6
6
  class Settings
7
- class MissingReviewCommandError < StandardError; end
8
-
9
- attr_reader :tool, :config
7
+ attr_reader :tool
10
8
 
11
9
  def initialize(tool, config: nil)
12
10
  @tool = tool
13
- @config = config || Reviewer.configuration.tools.fetch(tool.to_sym) { {} }
11
+ @config = config
12
+ end
14
13
 
15
- # Ideally, folks would fill out everything, but realistically, the 'review' command is the only required value.
16
- # If the key is missing, or maybe there was a typo, fail right away.
17
- raise MissingReviewCommandError, "'#{key}' does not have a 'review' key under 'commands' in `#{Reviewer.configuration.file}`" unless commands.key?(:review)
14
+ def ==(other)
15
+ self.class == other.class &&
16
+ state == other.state
18
17
  end
18
+ alias eql? ==
19
19
 
20
20
  def disabled?
21
21
  config.fetch(:disabled, false)
@@ -80,6 +80,16 @@ module Reviewer
80
80
  def quiet_option
81
81
  commands.fetch(:quiet_option, '')
82
82
  end
83
+
84
+ protected
85
+
86
+ def config
87
+ @config || Reviewer.tools.to_h.fetch(tool.to_sym) { {} }
88
+ end
89
+
90
+ def state
91
+ config.to_hash
92
+ end
83
93
  end
84
94
  end
85
95
  end
@@ -1,14 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reviewer
4
- # Provides a collection of the configured tools
5
- module Tools
6
- def self.all
7
- tools = []
8
- Reviewer.configuration.tools.each_key do |key|
9
- tools << Tool.new(key)
10
- end
11
- tools
4
+ # Provides convenient access to subsets of configured tools
5
+ class Tools
6
+ def initialize(tags: nil, tool_names: nil)
7
+ @tags = tags
8
+ @tool_names = tool_names
9
+ end
10
+
11
+ def to_h
12
+ configured
13
+ end
14
+
15
+ def all
16
+ configured.keys.map { |tool_name| Tool.new(tool_name) }
17
+ end
18
+ alias to_a all
19
+
20
+ def enabled
21
+ @enabled ||= all.keep_if(&:enabled?)
22
+ end
23
+
24
+ def specified
25
+ all.keep_if { |tool| named?(tool) }
26
+ end
27
+
28
+ def tagged
29
+ enabled.keep_if { |tool| tagged?(tool) }
30
+ end
31
+
32
+ def current
33
+ subset? ? (specified + tagged).uniq : enabled
34
+ end
35
+
36
+ private
37
+
38
+ def subset?
39
+ tool_names.any? || tags.any?
40
+ end
41
+
42
+ def configured
43
+ @configured ||= Loader.configuration
44
+ end
45
+
46
+ def tags
47
+ Array(@tags || Reviewer.arguments.tags)
48
+ end
49
+
50
+ def tool_names
51
+ Array(@tool_names || Reviewer.arguments.tool_names)
52
+ end
53
+
54
+ def tagged?(tool)
55
+ tool.enabled? && tags.intersection(tool.tags).any?
56
+ end
57
+
58
+ def named?(tool)
59
+ tool_names.map(&:to_s).include?(tool.key.to_s)
12
60
  end
13
61
  end
14
62
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reviewer
4
- VERSION = '0.1.2'
4
+ VERSION = '0.1.3'
5
5
  end
data/reviewer.gemspec CHANGED
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.add_dependency 'slop'
33
33
 
34
34
  spec.add_development_dependency 'bundler-audit'
35
+ spec.add_development_dependency 'inch'
35
36
  spec.add_development_dependency 'rubocop'
36
37
  spec.add_development_dependency 'rubocop-minitest'
37
38
  spec.add_development_dependency 'rubocop-rake'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reviewer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garrett Dimon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-04 00:00:00.000000000 Z
11
+ date: 2021-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: inch
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rubocop
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -111,12 +125,15 @@ dependencies:
111
125
  description: Provides a unified approach to managing automated code quality tools.
112
126
  email:
113
127
  - email@garrettdimon.com
114
- executables: []
128
+ executables:
129
+ - fmt
130
+ - rvw
115
131
  extensions: []
116
132
  extra_rdoc_files: []
117
133
  files:
118
134
  - ".github/workflows/main.yml"
119
135
  - ".gitignore"
136
+ - ".inch.yml"
120
137
  - ".reviewer.example.yml"
121
138
  - ".reviewer.yml"
122
139
  - ".rubocop.yml"
@@ -129,11 +146,16 @@ files:
129
146
  - README.md
130
147
  - Rakefile
131
148
  - bin/console
132
- - bin/fmt
133
- - bin/rvw
134
149
  - bin/setup
150
+ - exe/fmt
151
+ - exe/rvw
135
152
  - lib/reviewer.rb
136
153
  - lib/reviewer/arguments.rb
154
+ - lib/reviewer/arguments/files.rb
155
+ - lib/reviewer/arguments/keywords.rb
156
+ - lib/reviewer/arguments/keywords/git.rb
157
+ - lib/reviewer/arguments/keywords/git/staged.rb
158
+ - lib/reviewer/arguments/tags.rb
137
159
  - lib/reviewer/configuration.rb
138
160
  - lib/reviewer/loader.rb
139
161
  - lib/reviewer/logger.rb