spoom 1.0.6 → 1.1.1

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: 8b9a6d7f3818e0af29439ba291b6c05fa29a821de374ee5c8181c64a05f3f7a3
4
- data.tar.gz: 6d60e6fc252e6aebcfff2860778b1e2ac596740823a279aec1788ccaefa2fc4b
3
+ metadata.gz: b209723354af5be10930893accb559ecbb62afc1469d2d8a12f7a7af44d67d6c
4
+ data.tar.gz: 4bd2e43681783fceb004e5595218127b5838b5e09c47800c6d4d0905566e42f0
5
5
  SHA512:
6
- metadata.gz: d156b4d9c8f2edb2f68e1b5fc21b659d8bec0bc60fd9110f565889a620febce064cc3ee176228cd659f1b21ca0ab000a9790407537eb9bd18edbc41d601b963b
7
- data.tar.gz: a7e1bf03e8bd942bc0f2ac6379739bfd795d1642025e00ef33e198b6422c61598b23c753248a698568b7c1edde23d232604b76ccdfd9d1b7ef98139c74cc583a
6
+ metadata.gz: 99e8cccc29317edb58b84ba3a5713dfbfcf44c97bea40d2b4f30c256460983da7698847ea6fd9fc5a8c58c1aa6cb716e626a4540ac3e60302822b2d17efb194c
7
+ data.tar.gz: 055571522e43f963a1ee79a1cbd138d10d8ca685a252e5bfc64fc696abbf1812ae796bef764ab2d95359d1236012fe6cc45cfc48074997273f30df02e018a8b1
data/Gemfile CHANGED
@@ -8,6 +8,5 @@ gemspec
8
8
  group(:development) do
9
9
  gem('rubocop-shopify', require: false)
10
10
  gem('rubocop-sorbet', require: false)
11
- gem('byebug')
12
11
  gem('pry-byebug')
13
12
  end
data/README.md CHANGED
@@ -69,7 +69,7 @@ $ spoom files
69
69
  List all typechecking errors sorted by location:
70
70
 
71
71
  ```
72
- $ spoom tc -s
72
+ $ spoom tc -s loc
73
73
  ```
74
74
 
75
75
  List all typechecking errors sorted by error code first:
@@ -96,6 +96,31 @@ These options can be combined:
96
96
  $ spoom tc -s -c 7004 -l 10
97
97
  ```
98
98
 
99
+ Remove duplicated error lines:
100
+
101
+ ```
102
+ $ spoom tc -u
103
+ ```
104
+
105
+ Format each error line:
106
+
107
+ ```
108
+ $ spoom tc -f '%C - %F:%L: %M'
109
+ ```
110
+
111
+ Where:
112
+
113
+ * `%C` is the error code
114
+ * `%F` is the file the error is from
115
+ * `%L` is the line the error is from
116
+ * `%M` is the error message
117
+
118
+ Hide the `Errors: X` at the end of the list:
119
+
120
+ ```
121
+ $ spoom tc --no-count
122
+ ```
123
+
99
124
  #### Typing coverage
100
125
 
101
126
  Show metrics about the project contents and the typing coverage:
@@ -183,6 +208,30 @@ Bump the strictness from all files currently at `typed: false` to `typed: true`
183
208
  $ spoom bump --from false --to true -f
184
209
  ```
185
210
 
211
+ Bump the strictness from a list of files (one file by line):
212
+
213
+ ```
214
+ $ spoom bump --from false --to true -o list.txt
215
+ ```
216
+
217
+ Check if files can be bumped without applying any change:
218
+
219
+ ```
220
+ $ spoom bump --from false --to true --dry
221
+ ```
222
+
223
+ Bump files using a custom instance of Sorbet:
224
+
225
+ ```
226
+ $ spoom bump --from false --to true --sorbet /path/to/sorbet/bin
227
+ ```
228
+
229
+ Count the number of type-checking errors if all files were bumped to true:
230
+
231
+ ```
232
+ $ spoom bump --count-errors --dry
233
+ ```
234
+
186
235
  #### Interact with Sorbet LSP mode
187
236
 
188
237
  **Experimental**
@@ -273,7 +322,7 @@ Create an LSP client:
273
322
 
274
323
  ```rb
275
324
  client = Spoom::LSP::Client.new(
276
- Spoom::Config::SORBET_PATH,
325
+ Spoom::Sorbet::BIN_PATH,
277
326
  "--lsp",
278
327
  "--enable-all-experimental-lsp-features",
279
328
  "--disable-watchman",
data/lib/spoom.rb CHANGED
@@ -1,13 +1,31 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "colorize"
4
5
  require "sorbet-runtime"
5
6
 
6
7
  module Spoom
8
+ extend T::Sig
9
+
10
+ SPOOM_PATH = (Pathname.new(__FILE__) / ".." / "..").to_s
11
+
7
12
  class Error < StandardError; end
8
- end
9
13
 
10
- require "spoom/config"
14
+ sig do
15
+ params(
16
+ cmd: String,
17
+ arg: String,
18
+ path: String,
19
+ capture_err: T::Boolean
20
+ ).returns([String, T::Boolean])
21
+ end
22
+ def self.exec(cmd, *arg, path: '.', capture_err: false)
23
+ method = capture_err ? "popen2e" : "popen2"
24
+ Open3.send(method, [cmd, *arg].join(" "), chdir: path) do |_, o, t|
25
+ [o.read, T.cast(t.value, Process::Status).success?]
26
+ end
27
+ end
28
+ end
11
29
 
12
30
  require "spoom/sorbet"
13
31
  require "spoom/cli"
data/lib/spoom/cli.rb CHANGED
@@ -17,43 +17,54 @@ module Spoom
17
17
  extend T::Sig
18
18
  include Helper
19
19
 
20
- class_option :color, desc: "Use colors", type: :boolean, default: true
21
- class_option :path, desc: "Run spoom in a specific path", type: :string, default: ".", aliases: :p
20
+ class_option :color, type: :boolean, default: true, desc: "Use colors"
21
+ class_option :path, type: :string, default: ".", aliases: :p, desc: "Run spoom in a specific path"
22
+
22
23
  map T.unsafe(%w[--version -v] => :__print_version)
23
24
 
24
- desc "bump", "bump Sorbet sigils from `false` to `true` when no errors"
25
+ desc "bump", "Bump Sorbet sigils from `false` to `true` when no errors"
25
26
  subcommand "bump", Spoom::Cli::Bump
26
27
 
27
- desc "config", "manage Sorbet config"
28
+ desc "config", "Manage Sorbet config"
28
29
  subcommand "config", Spoom::Cli::Config
29
30
 
30
- desc "coverage", "collect metrics related to Sorbet coverage"
31
+ desc "coverage", "Collect metrics related to Sorbet coverage"
31
32
  subcommand "coverage", Spoom::Cli::Coverage
32
33
 
33
- desc "lsp", "send LSP requests to Sorbet"
34
+ desc "lsp", "Send LSP requests to Sorbet"
34
35
  subcommand "lsp", Spoom::Cli::LSP
35
36
 
36
- desc "tc", "run Sorbet and parses its output"
37
+ desc "tc", "Run Sorbet and parses its output"
37
38
  subcommand "tc", Spoom::Cli::Run
38
39
 
39
- desc "files", "list all the files typechecked by Sorbet"
40
+ desc "files", "List all the files typechecked by Sorbet"
41
+ option :tree, type: :boolean, default: true, desc: "Display list as an indented tree"
42
+ option :rbi, type: :boolean, default: true, desc: "Show RBI files"
40
43
  def files
41
44
  in_sorbet_project!
42
45
 
43
46
  path = exec_path
44
- config = Spoom::Sorbet::Config.parse_file(sorbet_config)
47
+ config = sorbet_config
45
48
  files = Spoom::Sorbet.srb_files(config, path: path)
46
49
 
47
- say("Files matching `#{sorbet_config}`:")
50
+ unless options[:rbi]
51
+ files = files.reject { |file| file.end_with?(".rbi") }
52
+ end
53
+
48
54
  if files.empty?
49
- say(" NONE")
50
- else
55
+ say_error("No file matching `#{sorbet_config_file}`")
56
+ exit(1)
57
+ end
58
+
59
+ if options[:tree]
51
60
  tree = FileTree.new(files, strip_prefix: path)
52
- tree.print(colors: options[:color], indent_level: 2)
61
+ tree.print(colors: options[:color], indent_level: 0)
62
+ else
63
+ puts files
53
64
  end
54
65
  end
55
66
 
56
- desc "--version", "show version"
67
+ desc "--version", "Show version"
57
68
  def __print_version
58
69
  puts "Spoom v#{Spoom::VERSION}"
59
70
  end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'find'
@@ -12,47 +12,140 @@ module Spoom
12
12
 
13
13
  default_task :bump
14
14
 
15
- desc "bump DIRECTORY", "change Sorbet sigils from one strictness to another when no errors"
16
- option :from, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_FALSE
17
- option :to, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_TRUE
18
- option :force, desc: "change strictness without type checking", type: :boolean, default: false, aliases: :f
15
+ desc "bump DIRECTORY", "Change Sorbet sigils from one strictness to another when no errors"
16
+ option :from, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_FALSE,
17
+ desc: "Change only files from this strictness"
18
+ option :to, type: :string, default: Spoom::Sorbet::Sigils::STRICTNESS_TRUE,
19
+ desc: "Change files to this strictness"
20
+ option :force, type: :boolean, default: false, aliases: :f,
21
+ desc: "Change strictness without type checking"
22
+ option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
23
+ option :dry, type: :boolean, default: false, aliases: :d,
24
+ desc: "Only display what would happen, do not actually change sigils"
25
+ option :only, type: :string, default: nil, aliases: :o,
26
+ desc: "Only change specified list (one file by line)"
27
+ option :suggest_bump_command, type: :string,
28
+ desc: "Command to suggest if files can be bumped"
29
+ option :count_errors, type: :boolean, default: false,
30
+ desc: "Count the number of errors if all files were bumped"
19
31
  sig { params(directory: String).void }
20
32
  def bump(directory = ".")
33
+ in_sorbet_project!
34
+
21
35
  from = options[:from]
22
36
  to = options[:to]
23
37
  force = options[:force]
38
+ dry = options[:dry]
39
+ only = options[:only]
40
+ cmd = options[:suggest_bump_command]
41
+ exec_path = File.expand_path(self.exec_path)
24
42
 
25
43
  unless Sorbet::Sigils.valid_strictness?(from)
26
- say_error("Invalid strictness #{from} for option --from")
44
+ say_error("Invalid strictness `#{from}` for option `--from`")
27
45
  exit(1)
28
46
  end
29
47
 
30
48
  unless Sorbet::Sigils.valid_strictness?(to)
31
- say_error("Invalid strictness #{to} for option --to")
49
+ say_error("Invalid strictness `#{to}` for option `--to`")
32
50
  exit(1)
33
51
  end
34
52
 
53
+ if options[:count_errors] && !dry
54
+ say_error("`--count-errors` can only be used with `--dry`")
55
+ exit(1)
56
+ end
57
+
58
+ say("Checking files...")
59
+
60
+ directory = File.expand_path(directory)
35
61
  files_to_bump = Sorbet::Sigils.files_with_sigil_strictness(directory, from)
36
62
 
63
+ files_from_config = config_files(path: exec_path)
64
+ files_to_bump.select! { |file| files_from_config.include?(file) }
65
+
66
+ if only
67
+ list = File.read(only).lines.map { |file| File.expand_path(file.strip) }
68
+ files_to_bump.select! { |file| list.include?(File.expand_path(file)) }
69
+ end
70
+
71
+ say("\n")
72
+
73
+ if files_to_bump.empty?
74
+ say("No file to bump from `#{from}` to `#{to}`")
75
+ exit(0)
76
+ end
77
+
37
78
  Sorbet::Sigils.change_sigil_in_files(files_to_bump, to)
38
79
 
39
- return [] if force
80
+ if force
81
+ print_changes(files_to_bump, command: cmd, from: from, to: to, dry: dry, path: exec_path)
82
+ undo_changes(files_to_bump, from) if dry
83
+ exit(files_to_bump.empty?)
84
+ end
40
85
 
41
- output, no_errors = Sorbet.srb_tc(path: File.expand_path(directory), capture_err: true)
86
+ output, no_errors = Sorbet.srb_tc(
87
+ "--no-error-sections",
88
+ path: exec_path,
89
+ capture_err: true,
90
+ sorbet_bin: options[:sorbet]
91
+ )
42
92
 
43
- return [] if no_errors
93
+ if no_errors
94
+ print_changes(files_to_bump, command: cmd, from: from, to: to, dry: dry, path: exec_path)
95
+ undo_changes(files_to_bump, from) if dry
96
+ exit(files_to_bump.empty?)
97
+ end
44
98
 
45
99
  errors = Sorbet::Errors::Parser.parse_string(output)
46
100
 
47
101
  files_with_errors = errors.map do |err|
48
- path = err.file
49
- File.join(directory, path) if path && File.file?(path)
102
+ path = File.expand_path(err.file)
103
+ next unless path.start_with?(directory)
104
+ next unless File.file?(path)
105
+ path
50
106
  end.compact.uniq
51
107
 
52
- Sorbet::Sigils.change_sigil_in_files(files_with_errors, from)
108
+ undo_changes(files_with_errors, from)
109
+
110
+ say("Found #{errors.length} type checking error#{'s' if errors.length > 1}") if options[:count_errors]
111
+
112
+ files_changed = files_to_bump - files_with_errors
113
+ print_changes(files_changed, command: cmd, from: from, to: to, dry: dry, path: exec_path)
114
+ undo_changes(files_to_bump, from) if dry
115
+ exit(files_changed.empty?)
53
116
  end
54
117
 
55
118
  no_commands do
119
+ def print_changes(files, command:, from: "false", to: "true", dry: false, path: File.expand_path("."))
120
+ if files.empty?
121
+ say("No file to bump from `#{from}` to `#{to}`")
122
+ return
123
+ end
124
+ message = StringIO.new
125
+ message << (dry ? "Can bump" : "Bumped")
126
+ message << " `#{files.size}` file#{'s' if files.size > 1}"
127
+ message << " from `#{from}` to `#{to}`:"
128
+ say(message.string)
129
+ files.each do |file|
130
+ file_path = Pathname.new(file).relative_path_from(path)
131
+ say(" + #{file_path}")
132
+ end
133
+ if dry && command
134
+ say("\nRun `#{command}` to bump them")
135
+ elsif dry
136
+ say("\nRun `spoom bump --from #{from} --to #{to}` to bump them")
137
+ end
138
+ end
139
+
140
+ def undo_changes(files, from_strictness)
141
+ Sorbet::Sigils.change_sigil_in_files(files, from_strictness)
142
+ end
143
+
144
+ def config_files(path: ".")
145
+ config = sorbet_config
146
+ files = Sorbet.srb_files(config, path: path)
147
+ files.map { |file| File.expand_path(file) }
148
+ end
56
149
  end
57
150
  end
58
151
  end
@@ -11,12 +11,12 @@ module Spoom
11
11
 
12
12
  default_task :show
13
13
 
14
- desc "show", "show Sorbet config"
14
+ desc "show", "Show Sorbet config"
15
15
  def show
16
16
  in_sorbet_project!
17
- config = Spoom::Sorbet::Config.parse_file(sorbet_config)
17
+ config = sorbet_config
18
18
 
19
- say("Found Sorbet config at `#{sorbet_config}`.")
19
+ say("Found Sorbet config at `#{sorbet_config_file}`.")
20
20
 
21
21
  say("\nPaths typechecked:")
22
22
  if config.paths.empty?
@@ -13,13 +13,16 @@ module Spoom
13
13
 
14
14
  default_task :snapshot
15
15
 
16
- desc "snapshot", "run srb tc and display metrics"
17
- option :save, type: :string, desc: "Save snapshot data as json", lazy_default: DATA_DIR
16
+ desc "snapshot", "Run srb tc and display metrics"
17
+ option :save, type: :string, lazy_default: DATA_DIR, desc: "Save snapshot data as json"
18
+ option :rbi, type: :boolean, default: true, desc: "Include RBI files in metrics"
19
+ option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
18
20
  def snapshot
19
21
  in_sorbet_project!
20
-
21
22
  path = exec_path
22
- snapshot = Spoom::Coverage.snapshot(path: path)
23
+ sorbet = options[:sorbet]
24
+
25
+ snapshot = Spoom::Coverage.snapshot(path: path, rbi: options[:rbi], sorbet_bin: sorbet)
23
26
  snapshot.print
24
27
 
25
28
  save_dir = options[:save]
@@ -27,29 +30,35 @@ module Spoom
27
30
  FileUtils.mkdir_p(save_dir)
28
31
  file = "#{save_dir}/#{snapshot.commit_sha || snapshot.timestamp}.json"
29
32
  File.write(file, snapshot.to_json)
30
- puts "\nSnapshot data saved under #{file}"
33
+ say("\nSnapshot data saved under `#{file}`")
31
34
  end
32
35
 
33
- desc "timeline", "replay a project and collect metrics"
34
- option :from, type: :string
35
- option :to, type: :string, default: Time.now.strftime("%F")
36
- option :save, type: :string, desc: "Save snapshot data as json", lazy_default: DATA_DIR
36
+ desc "timeline", "Replay a project and collect metrics"
37
+ option :from, type: :string, desc: "From commit date"
38
+ option :to, type: :string, default: Time.now.strftime("%F"), desc: "To commit date"
39
+ option :save, type: :string, lazy_default: DATA_DIR, desc: "Save snapshot data as json"
37
40
  option :bundle_install, type: :boolean, desc: "Execute `bundle install` before collecting metrics"
41
+ option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
38
42
  def timeline
39
43
  in_sorbet_project!
40
44
  path = exec_path
45
+ sorbet = options[:sorbet]
41
46
 
42
47
  sha_before = Spoom::Git.last_commit(path: path)
43
48
  unless sha_before
44
49
  say_error("Not in a git repository")
45
- $stderr.puts "\nSpoom needs to checkout into your previous commits to build the timeline."
50
+ say_error("\nSpoom needs to checkout into your previous commits to build the timeline.", status: nil)
46
51
  exit(1)
47
52
  end
48
53
 
49
54
  unless Spoom::Git.workdir_clean?(path: path)
50
55
  say_error("Uncommited changes")
51
- $stderr.puts "\nSpoom needs to checkout into your previous commits to build the timeline."
52
- $stderr.puts "\nPlease git commit or git stash your changes then try again."
56
+ say_error(<<~ERR, status: nil)
57
+
58
+ Spoom needs to checkout into your previous commits to build the timeline."
59
+
60
+ Please `git commit` or `git stash` your changes then try again
61
+ ERR
53
62
  exit(1)
54
63
  end
55
64
 
@@ -69,13 +78,13 @@ module Spoom
69
78
  ticks = timeline.ticks
70
79
 
71
80
  if ticks.empty?
72
- say_error("No commits to replay, try different --from and --to options")
81
+ say_error("No commits to replay, try different `--from` and `--to` options")
73
82
  exit(1)
74
83
  end
75
84
 
76
85
  ticks.each_with_index do |sha, i|
77
86
  date = Spoom::Git.commit_time(sha, path: path)
78
- puts "Analyzing commit #{sha} - #{date&.strftime('%F')} (#{i + 1} / #{ticks.size})"
87
+ say("Analyzing commit `#{sha}` - #{date&.strftime('%F')} (#{i + 1} / #{ticks.size})")
79
88
 
80
89
  Spoom::Git.checkout(sha, path: path)
81
90
 
@@ -83,32 +92,38 @@ module Spoom
83
92
  if options[:bundle_install]
84
93
  Bundler.with_clean_env do
85
94
  next unless bundle_install(path, sha)
86
- snapshot = Spoom::Coverage.snapshot(path: path)
95
+ snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
87
96
  end
88
97
  else
89
- snapshot = Spoom::Coverage.snapshot(path: path)
98
+ snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
90
99
  end
91
100
  next unless snapshot
92
101
 
93
102
  snapshot.print(indent_level: 2)
94
- puts "\n"
103
+ say("\n")
95
104
 
96
105
  next unless save_dir
97
106
  file = "#{save_dir}/#{sha}.json"
98
107
  File.write(file, snapshot.to_json)
99
- puts " Snapshot data saved under #{file}\n\n"
108
+ say(" Snapshot data saved under `#{file}`\n\n")
100
109
  end
101
110
  Spoom::Git.checkout(sha_before, path: path)
102
111
  end
103
112
 
104
- desc "report", "produce a typing coverage report"
105
- option :data, type: :string, desc: "Snapshots JSON data", default: DATA_DIR
106
- option :file, type: :string, default: "spoom_report.html", aliases: :f
107
- option :color_ignore, type: :string, default: Spoom::Coverage::D3::COLOR_IGNORE
108
- option :color_false, type: :string, default: Spoom::Coverage::D3::COLOR_FALSE
109
- option :color_true, type: :string, default: Spoom::Coverage::D3::COLOR_TRUE
110
- option :color_strict, type: :string, default: Spoom::Coverage::D3::COLOR_STRICT
111
- option :color_strong, type: :string, default: Spoom::Coverage::D3::COLOR_STRONG
113
+ desc "report", "Produce a typing coverage report"
114
+ option :data, type: :string, default: DATA_DIR, desc: "Snapshots JSON data"
115
+ option :file, type: :string, default: "spoom_report.html", aliases: :f,
116
+ desc: "Save report to file"
117
+ option :color_ignore, type: :string, default: Spoom::Coverage::D3::COLOR_IGNORE,
118
+ desc: "Color used for typed: ignore"
119
+ option :color_false, type: :string, default: Spoom::Coverage::D3::COLOR_FALSE,
120
+ desc: "Color used for typed: false"
121
+ option :color_true, type: :string, default: Spoom::Coverage::D3::COLOR_TRUE,
122
+ desc: "Color used for typed: true"
123
+ option :color_strict, type: :string, default: Spoom::Coverage::D3::COLOR_STRICT,
124
+ desc: "Color used for typed: strict"
125
+ option :color_strong, type: :string, default: Spoom::Coverage::D3::COLOR_STRONG,
126
+ desc: "Color used for typed: strong"
112
127
  def report
113
128
  in_sorbet_project!
114
129
 
@@ -135,20 +150,20 @@ module Spoom
135
150
  report = Spoom::Coverage.report(snapshots, palette: palette, path: exec_path)
136
151
  file = options[:file]
137
152
  File.write(file, report.html)
138
- puts "Report generated under #{file}"
139
- puts "\nUse #{colorize('spoom coverage open', :blue)} to open it."
153
+ say("Report generated under `#{file}`")
154
+ say("\nUse `spoom coverage open` to open it.")
140
155
  end
141
156
 
142
- desc "open", "open the typing coverage report"
157
+ desc "open", "Open the typing coverage report"
143
158
  def open(file = "spoom_report.html")
144
159
  unless File.exist?(file)
145
- say_error("No report file to open #{colorize(file, :blue)}")
146
- $stderr.puts <<~OUT
160
+ say_error("No report file to open `#{file}`")
161
+ say_error(<<~ERR, status: nil)
147
162
 
148
- If you already generated a report under another name use #{colorize('spoom coverage open PATH', :blue)}.
163
+ If you already generated a report under another name use #{blue('spoom coverage open PATH')}.
149
164
 
150
- To generate a report run #{colorize('spoom coverage report', :blue)}.
151
- OUT
165
+ To generate a report run #{blue('spoom coverage report')}.
166
+ ERR
152
167
  exit(1)
153
168
  end
154
169
 
@@ -160,7 +175,7 @@ module Spoom
160
175
  return nil unless string
161
176
  Time.parse(string)
162
177
  rescue ArgumentError
163
- say_error("Invalid date `#{string}` for option #{option} (expected format YYYY-MM-DD)")
178
+ say_error("Invalid date `#{string}` for option `#{option}` (expected format `YYYY-MM-DD`)")
164
179
  exit(1)
165
180
  end
166
181
 
@@ -169,21 +184,21 @@ module Spoom
169
184
  opts[:chdir] = path
170
185
  out, status = Open3.capture2e("bundle install", opts)
171
186
  unless status.success?
172
- say_error("Can't run `bundle install` for commit #{sha}. Skipping snapshot")
173
- $stderr.puts(out)
187
+ say_error("Can't run `bundle install` for commit `#{sha}`. Skipping snapshot")
188
+ say_error(out, status: nil)
174
189
  return false
175
190
  end
176
191
  true
177
192
  end
178
193
 
179
194
  def message_no_data(file)
180
- say_error("No snapshot files found in #{colorize(file, :blue)}")
181
- $stderr.puts <<~OUT
195
+ say_error("No snapshot files found in `#{file}`")
196
+ say_error(<<~ERR, status: nil)
182
197
 
183
- If you already generated snapshot files under another directory use #{colorize('spoom coverage report PATH', :blue)}.
198
+ If you already generated snapshot files under another directory use #{blue('spoom coverage report PATH')}.
184
199
 
185
- To generate snapshot files run #{colorize('spoom coverage timeline --save-dir spoom_data', :blue)}.
186
- OUT
200
+ To generate snapshot files run #{blue('spoom coverage timeline --save-dir spoom_data')}.
201
+ ERR
187
202
  end
188
203
  end
189
204
  end