spoom 1.0.5 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a5f19185a1beba98f75c9c96b08c06400c9917c0d85734f1dbf9c9d77e3fa74
4
- data.tar.gz: 53f4b8b10f81b19588f58d57c93349721230836e3b98524b52c46a78f4682365
3
+ metadata.gz: c8ab95447c622919ce44c084fd3a3e6cfeb6c239f466bf956ba408909c4765b8
4
+ data.tar.gz: 52ec4172e6b9ac96ab64c813bcd92ac71912903d64e2af850864454820f20a09
5
5
  SHA512:
6
- metadata.gz: aff5a2bc4b3bd01c815ad0d00e76fcc700728ec659135645dce259db72dc97cf8ea2d339827d979bf9f225637b7d23f2ea09d13dd02786229fe5c42caf88886e
7
- data.tar.gz: b2760f8f58a7f3cbfd90d59e16f5bb9b76356f13fc3f38a606758652f53caf72004024128e4195451ca32f30c9d03c6bdb553a6a7aea2893c7f0e6a2e6ec2528
6
+ metadata.gz: 19f29b380868435e4fb21d27004541a4a319a6c4eae1df5603a22f11698b0ef814bb4a6175a3932a9288ec92f7f8998e961735033992cbfa91d64a7f1ee1b438
7
+ data.tar.gz: 2da177e08833e1310d280bbf228e6763fbd3085e7d10780925acca0d50293062186f4b64b971e2ab436bae6f8120bbdd6c26bf6b04e25c92906a9f2cefa68751
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,24 @@ 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
+
186
229
  #### Interact with Sorbet LSP mode
187
230
 
188
231
  **Experimental**
@@ -273,7 +316,7 @@ Create an LSP client:
273
316
 
274
317
  ```rb
275
318
  client = Spoom::LSP::Client.new(
276
- Spoom::Config::SORBET_PATH,
319
+ Spoom::Sorbet::BIN_PATH,
277
320
  "--lsp",
278
321
  "--enable-all-experimental-lsp-features",
279
322
  "--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: "Exclude RBI files from 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