spoom 1.1.11 → 1.1.13
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 +4 -4
- data/Gemfile +1 -0
- data/README.md +6 -0
- data/Rakefile +1 -0
- data/lib/spoom/cli/bump.rb +17 -12
- data/lib/spoom/cli/coverage.rb +20 -17
- data/lib/spoom/cli/helper.rb +6 -5
- data/lib/spoom/cli/lsp.rb +4 -3
- data/lib/spoom/cli/run.rb +18 -8
- data/lib/spoom/cli.rb +6 -4
- data/lib/spoom/context.rb +219 -0
- data/lib/spoom/coverage/d3/base.rb +12 -8
- data/lib/spoom/coverage/d3/circle_map.rb +40 -37
- data/lib/spoom/coverage/d3/pie.rb +41 -30
- data/lib/spoom/coverage/d3/timeline.rb +95 -88
- data/lib/spoom/coverage/d3.rb +72 -70
- data/lib/spoom/coverage/report.rb +6 -6
- data/lib/spoom/coverage/snapshot.rb +65 -34
- data/lib/spoom/coverage.rb +104 -81
- data/lib/spoom/file_tree.rb +5 -3
- data/lib/spoom/git.rb +102 -96
- data/lib/spoom/printer.rb +4 -0
- data/lib/spoom/sorbet/errors.rb +30 -12
- data/lib/spoom/sorbet/lsp/base.rb +1 -1
- data/lib/spoom/sorbet/lsp/errors.rb +23 -17
- data/lib/spoom/sorbet/lsp/structures.rb +86 -55
- data/lib/spoom/sorbet/lsp.rb +78 -70
- data/lib/spoom/sorbet/metrics.rb +18 -16
- data/lib/spoom/sorbet/sigils.rb +59 -54
- data/lib/spoom/sorbet.rb +17 -14
- data/lib/spoom/timeline.rb +6 -5
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom.rb +43 -25
- metadata +20 -20
- data/lib/spoom/test_helpers/project.rb +0 -138
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5cad6017cf76302bc359d170fce6c2e33c4ff5923a564b26e2f270b42c102e3d
|
4
|
+
data.tar.gz: 734654483394f8520346d0ddab7872e1c7f42de4eed7523e503264f8045bf2c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a67da9fa2811e15d5bfc5f554a2b1cb23bed4a0744617d1168b3d3d4c08f4f6828b207142996fb8cebc57ca8732d901e3e390c43939eaa504a2ed1cc1fe611b4
|
7
|
+
data.tar.gz: 1b98af8f37acd554f908fdde1257f9c53eaab0a37b21847e619caee5cdda44e9f06e248545ec247cb43c6ac434d5e00bce4b6731eb434c62bd3a60a72c67313f
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -121,6 +121,12 @@ Hide the `Errors: X` at the end of the list:
|
|
121
121
|
$ spoom tc --no-count
|
122
122
|
```
|
123
123
|
|
124
|
+
List only the errors comming from specific directories or files:
|
125
|
+
|
126
|
+
```
|
127
|
+
$ spoom tc file1.rb path1/ path2/
|
128
|
+
```
|
129
|
+
|
124
130
|
#### Typing coverage
|
125
131
|
|
126
132
|
Show metrics about the project contents and the typing coverage:
|
data/Rakefile
CHANGED
data/lib/spoom/cli/bump.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "find"
|
5
|
+
require "open3"
|
6
6
|
|
7
7
|
module Spoom
|
8
8
|
module Cli
|
@@ -71,7 +71,7 @@ module Spoom
|
|
71
71
|
say("\n")
|
72
72
|
|
73
73
|
if files_to_bump.empty?
|
74
|
-
say("No
|
74
|
+
say("No files to bump from `#{from}` to `#{to}`")
|
75
75
|
exit(0)
|
76
76
|
end
|
77
77
|
|
@@ -85,11 +85,10 @@ module Spoom
|
|
85
85
|
|
86
86
|
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
87
87
|
result = Sorbet.srb_tc(
|
88
|
-
"--no-error-sections",
|
89
88
|
"--error-url-base=#{error_url_base}",
|
90
89
|
path: exec_path,
|
91
90
|
capture_err: true,
|
92
|
-
sorbet_bin: options[:sorbet]
|
91
|
+
sorbet_bin: options[:sorbet],
|
93
92
|
)
|
94
93
|
|
95
94
|
check_sorbet_segfault(result.exit_code) do
|
@@ -108,17 +107,22 @@ module Spoom
|
|
108
107
|
|
109
108
|
errors = Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
|
110
109
|
|
111
|
-
|
112
|
-
|
110
|
+
all_files = errors.flat_map do |err|
|
111
|
+
[err.file, *err.files_from_error_sections]
|
112
|
+
end
|
113
|
+
|
114
|
+
files_with_errors = all_files.map do |file|
|
115
|
+
path = File.expand_path(file)
|
113
116
|
next unless path.start_with?(directory)
|
114
117
|
next unless File.file?(path)
|
115
118
|
next unless files_to_bump.include?(path)
|
119
|
+
|
116
120
|
path
|
117
121
|
end.compact.uniq
|
118
122
|
|
119
123
|
undo_changes(files_with_errors, from)
|
120
124
|
|
121
|
-
say("Found #{errors.length} type checking error#{
|
125
|
+
say("Found #{errors.length} type checking error#{"s" if errors.length > 1}") if options[:count_errors]
|
122
126
|
|
123
127
|
files_changed = files_to_bump - files_with_errors
|
124
128
|
print_changes(files_changed, command: cmd, from: from, to: to, dry: dry, path: exec_path)
|
@@ -128,13 +132,14 @@ module Spoom
|
|
128
132
|
|
129
133
|
no_commands do
|
130
134
|
def print_changes(files, command:, from: "false", to: "true", dry: false, path: File.expand_path("."))
|
131
|
-
|
132
|
-
|
135
|
+
files_count = files.size
|
136
|
+
if files_count.zero?
|
137
|
+
say("No files to bump from `#{from}` to `#{to}`")
|
133
138
|
return
|
134
139
|
end
|
135
140
|
message = StringIO.new
|
136
141
|
message << (dry ? "Can bump" : "Bumped")
|
137
|
-
message << " `#{
|
142
|
+
message << " `#{files_count}` file#{"s" if files_count > 1}"
|
138
143
|
message << " from `#{from}` to `#{to}`:"
|
139
144
|
say(message.string)
|
140
145
|
files.each do |file|
|
@@ -142,7 +147,7 @@ module Spoom
|
|
142
147
|
say(" + #{file_path}")
|
143
148
|
end
|
144
149
|
if dry && command
|
145
|
-
say("\nRun `#{command}` to bump them")
|
150
|
+
say("\nRun `#{command}` to bump #{files_count > 1 ? "them" : "it"}")
|
146
151
|
elsif dry
|
147
152
|
say("\nRun `spoom bump --from #{from} --to #{to}` locally then `commit the changes` and `push them`")
|
148
153
|
end
|
data/lib/spoom/cli/coverage.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require_relative
|
5
|
-
require_relative
|
4
|
+
require_relative "../coverage"
|
5
|
+
require_relative "../timeline"
|
6
6
|
|
7
7
|
module Spoom
|
8
8
|
module Cli
|
@@ -27,6 +27,7 @@ module Spoom
|
|
27
27
|
|
28
28
|
save_dir = options[:save]
|
29
29
|
return unless save_dir
|
30
|
+
|
30
31
|
FileUtils.mkdir_p(save_dir)
|
31
32
|
file = "#{save_dir}/#{snapshot.commit_sha || snapshot.timestamp}.json"
|
32
33
|
File.write(file, snapshot.to_json)
|
@@ -45,7 +46,7 @@ module Spoom
|
|
45
46
|
sorbet = options[:sorbet]
|
46
47
|
|
47
48
|
ref_before = Spoom::Git.current_branch
|
48
|
-
ref_before = Spoom::Git.last_commit(path: path) unless ref_before
|
49
|
+
ref_before = Spoom::Git.last_commit(path: path)&.sha unless ref_before
|
49
50
|
unless ref_before
|
50
51
|
say_error("Not in a git repository")
|
51
52
|
say_error("\nSpoom needs to checkout into your previous commits to build the timeline.", status: nil)
|
@@ -70,9 +71,9 @@ module Spoom
|
|
70
71
|
to = parse_time(options[:to], "--to")
|
71
72
|
|
72
73
|
unless from
|
73
|
-
|
74
|
-
|
75
|
-
from =
|
74
|
+
intro_commit = Spoom::Git.sorbet_intro_commit(path: path)
|
75
|
+
intro_commit = T.must(intro_commit) # we know it's in there since in_sorbet_project!
|
76
|
+
from = intro_commit.time
|
76
77
|
end
|
77
78
|
|
78
79
|
timeline = Spoom::Timeline.new(from, to, path: path)
|
@@ -83,16 +84,16 @@ module Spoom
|
|
83
84
|
exit(1)
|
84
85
|
end
|
85
86
|
|
86
|
-
ticks.each_with_index do |
|
87
|
-
|
88
|
-
say("Analyzing commit `#{sha}` - #{date&.strftime('%F')} (#{i + 1} / #{ticks.size})")
|
87
|
+
ticks.each_with_index do |commit, i|
|
88
|
+
say("Analyzing commit `#{commit.sha}` - #{commit.time.strftime("%F")} (#{i + 1} / #{ticks.size})")
|
89
89
|
|
90
|
-
Spoom::Git.checkout(sha, path: path)
|
90
|
+
Spoom::Git.checkout(commit.sha, path: path)
|
91
91
|
|
92
92
|
snapshot = T.let(nil, T.nilable(Spoom::Coverage::Snapshot))
|
93
93
|
if options[:bundle_install]
|
94
94
|
Bundler.with_unbundled_env do
|
95
|
-
next unless bundle_install(path, sha)
|
95
|
+
next unless bundle_install(path, commit.sha)
|
96
|
+
|
96
97
|
snapshot = Spoom::Coverage.snapshot(path: path, sorbet_bin: sorbet)
|
97
98
|
end
|
98
99
|
else
|
@@ -104,7 +105,8 @@ module Spoom
|
|
104
105
|
say("\n")
|
105
106
|
|
106
107
|
next unless save_dir
|
107
|
-
|
108
|
+
|
109
|
+
file = "#{save_dir}/#{commit.sha}.json"
|
108
110
|
File.write(file, snapshot.to_json)
|
109
111
|
say(" Snapshot data saved under `#{file}`\n\n")
|
110
112
|
end
|
@@ -145,7 +147,7 @@ module Spoom
|
|
145
147
|
false: options[:color_false],
|
146
148
|
true: options[:color_true],
|
147
149
|
strict: options[:color_strict],
|
148
|
-
strong: options[:color_strong]
|
150
|
+
strong: options[:color_strong],
|
149
151
|
)
|
150
152
|
|
151
153
|
report = Spoom::Coverage.report(snapshots, palette: palette, path: exec_path)
|
@@ -161,9 +163,9 @@ module Spoom
|
|
161
163
|
say_error("No report file to open `#{file}`")
|
162
164
|
say_error(<<~ERR, status: nil)
|
163
165
|
|
164
|
-
If you already generated a report under another name use #{blue(
|
166
|
+
If you already generated a report under another name use #{blue("spoom coverage open PATH")}.
|
165
167
|
|
166
|
-
To generate a report run #{blue(
|
168
|
+
To generate a report run #{blue("spoom coverage report")}.
|
167
169
|
ERR
|
168
170
|
exit(1)
|
169
171
|
end
|
@@ -174,6 +176,7 @@ module Spoom
|
|
174
176
|
no_commands do
|
175
177
|
def parse_time(string, option)
|
176
178
|
return nil unless string
|
179
|
+
|
177
180
|
Time.parse(string)
|
178
181
|
rescue ArgumentError
|
179
182
|
say_error("Invalid date `#{string}` for option `#{option}` (expected format `YYYY-MM-DD`)")
|
@@ -196,9 +199,9 @@ module Spoom
|
|
196
199
|
say_error("No snapshot files found in `#{file}`")
|
197
200
|
say_error(<<~ERR, status: nil)
|
198
201
|
|
199
|
-
If you already generated snapshot files under another directory use #{blue(
|
202
|
+
If you already generated snapshot files under another directory use #{blue("spoom coverage report PATH")}.
|
200
203
|
|
201
|
-
To generate snapshot files run #{blue(
|
204
|
+
To generate snapshot files run #{blue("spoom coverage timeline --save")}.
|
202
205
|
ERR
|
203
206
|
end
|
204
207
|
end
|
data/lib/spoom/cli/helper.rb
CHANGED
@@ -33,7 +33,7 @@ module Spoom
|
|
33
33
|
params(
|
34
34
|
message: String,
|
35
35
|
status: T.nilable(String),
|
36
|
-
nl: T::Boolean
|
36
|
+
nl: T::Boolean,
|
37
37
|
).void
|
38
38
|
end
|
39
39
|
def say_error(message, status: "Error", nl: true)
|
@@ -60,8 +60,8 @@ module Spoom
|
|
60
60
|
unless in_sorbet_project?
|
61
61
|
say_error(
|
62
62
|
"not in a Sorbet project (`#{sorbet_config_file}` not found)\n\n" \
|
63
|
-
|
64
|
-
|
63
|
+
"When running spoom from another path than the project's root, " \
|
64
|
+
"use `--path PATH` to specify the path to the root.",
|
65
65
|
)
|
66
66
|
Kernel.exit(1)
|
67
67
|
end
|
@@ -116,9 +116,9 @@ module Spoom
|
|
116
116
|
word = StringIO.new
|
117
117
|
in_ticks = T.let(false, T::Boolean)
|
118
118
|
string.chars.each do |c|
|
119
|
-
if c ==
|
119
|
+
if c == "`" && !in_ticks
|
120
120
|
in_ticks = true
|
121
|
-
elsif c ==
|
121
|
+
elsif c == "`" && in_ticks
|
122
122
|
in_ticks = false
|
123
123
|
res << colorize(word.string, HIGHLIGHT_COLOR)
|
124
124
|
word = StringIO.new
|
@@ -135,6 +135,7 @@ module Spoom
|
|
135
135
|
sig { params(string: String, color: Color).returns(String) }
|
136
136
|
def colorize(string, *color)
|
137
137
|
return string unless color?
|
138
|
+
|
138
139
|
T.unsafe(self).set_color(string, *color)
|
139
140
|
end
|
140
141
|
|
data/lib/spoom/cli/lsp.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
4
|
+
require "shellwords"
|
5
5
|
|
6
6
|
require_relative "../sorbet/lsp"
|
7
7
|
|
@@ -28,6 +28,7 @@ module Spoom
|
|
28
28
|
Dir["**/*.rb"].each do |file|
|
29
29
|
res = client.document_symbols(to_uri(file))
|
30
30
|
next if res.empty?
|
31
|
+
|
31
32
|
say("Symbols from `#{file}`:")
|
32
33
|
printer.print_objects(res)
|
33
34
|
end
|
@@ -117,7 +118,7 @@ module Spoom
|
|
117
118
|
"--lsp",
|
118
119
|
"--enable-all-experimental-lsp-features",
|
119
120
|
"--disable-watchman",
|
120
|
-
path: path
|
121
|
+
path: path,
|
121
122
|
)
|
122
123
|
client.open(File.expand_path(path))
|
123
124
|
client
|
@@ -127,7 +128,7 @@ module Spoom
|
|
127
128
|
Spoom::LSP::SymbolPrinter.new(
|
128
129
|
indent_level: 2,
|
129
130
|
colors: options[:color],
|
130
|
-
prefix: "file://#{File.expand_path(exec_path)}"
|
131
|
+
prefix: "file://#{File.expand_path(exec_path)}",
|
131
132
|
)
|
132
133
|
end
|
133
134
|
|
data/lib/spoom/cli/run.rb
CHANGED
@@ -22,7 +22,8 @@ module Spoom
|
|
22
22
|
option :uniq, type: :boolean, aliases: :u, desc: "Remove duplicated lines"
|
23
23
|
option :count, type: :boolean, default: true, desc: "Show errors count"
|
24
24
|
option :sorbet, type: :string, desc: "Path to custom Sorbet bin"
|
25
|
-
|
25
|
+
option :sorbet_options, type: :string, default: "", desc: "Pass options to Sorbet"
|
26
|
+
def tc(*paths_to_select)
|
26
27
|
in_sorbet_project!
|
27
28
|
|
28
29
|
path = exec_path
|
@@ -36,10 +37,10 @@ module Spoom
|
|
36
37
|
|
37
38
|
unless limit || code || sort
|
38
39
|
result = T.unsafe(Spoom::Sorbet).srb_tc(
|
39
|
-
*
|
40
|
+
*options[:sorbet_options].split(" "),
|
40
41
|
path: path,
|
41
42
|
capture_err: false,
|
42
|
-
sorbet_bin: sorbet
|
43
|
+
sorbet_bin: sorbet,
|
43
44
|
)
|
44
45
|
|
45
46
|
check_sorbet_segfault(result.code)
|
@@ -47,11 +48,13 @@ module Spoom
|
|
47
48
|
exit(result.status)
|
48
49
|
end
|
49
50
|
|
51
|
+
error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
|
50
52
|
result = T.unsafe(Spoom::Sorbet).srb_tc(
|
51
|
-
*
|
53
|
+
*options[:sorbet_options].split(" "),
|
54
|
+
"--error-url-base=#{error_url_base}",
|
52
55
|
path: path,
|
53
56
|
capture_err: true,
|
54
|
-
sorbet_bin: sorbet
|
57
|
+
sorbet_bin: sorbet,
|
55
58
|
)
|
56
59
|
|
57
60
|
check_sorbet_segfault(result.exit_code)
|
@@ -61,9 +64,17 @@ module Spoom
|
|
61
64
|
exit(0)
|
62
65
|
end
|
63
66
|
|
64
|
-
errors = Spoom::Sorbet::Errors::Parser.parse_string(result.err)
|
67
|
+
errors = Spoom::Sorbet::Errors::Parser.parse_string(result.err, error_url_base: error_url_base)
|
65
68
|
errors_count = errors.size
|
66
69
|
|
70
|
+
errors = errors.select { |e| e.code == code } if code
|
71
|
+
|
72
|
+
unless paths_to_select.empty?
|
73
|
+
errors.select! do |error|
|
74
|
+
paths_to_select.any? { |path_to_select| error.file&.start_with?(path_to_select) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
67
78
|
errors = case sort
|
68
79
|
when SORT_CODE
|
69
80
|
Spoom::Sorbet::Errors.sort_errors_by_code(errors)
|
@@ -73,7 +84,6 @@ module Spoom
|
|
73
84
|
errors # preserve natural sort
|
74
85
|
end
|
75
86
|
|
76
|
-
errors = errors.select { |e| e.code == code } if code
|
77
87
|
errors = T.must(errors.slice(0, limit)) if limit
|
78
88
|
|
79
89
|
lines = errors.map { |e| format_error(e, format || DEFAULT_FORMAT) }
|
@@ -110,7 +120,7 @@ module Spoom
|
|
110
120
|
cyan = T.let(false, T::Boolean)
|
111
121
|
word = StringIO.new
|
112
122
|
message.chars.each do |c|
|
113
|
-
if c ==
|
123
|
+
if c == "`"
|
114
124
|
cyan = !cyan
|
115
125
|
next
|
116
126
|
end
|
data/lib/spoom/cli.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
require "thor"
|
5
5
|
|
6
|
-
require_relative
|
6
|
+
require_relative "cli/helper"
|
7
7
|
|
8
8
|
require_relative "cli/bump"
|
9
9
|
require_relative "cli/config"
|
@@ -20,7 +20,7 @@ module Spoom
|
|
20
20
|
class_option :color, type: :boolean, default: true, desc: "Use colors"
|
21
21
|
class_option :path, type: :string, default: ".", aliases: :p, desc: "Run spoom in a specific path"
|
22
22
|
|
23
|
-
map T.unsafe(
|
23
|
+
map T.unsafe(["--version", "-v"] => :__print_version)
|
24
24
|
|
25
25
|
desc "bump", "Bump Sorbet sigils from `false` to `true` when no errors"
|
26
26
|
subcommand "bump", Spoom::Cli::Bump
|
@@ -71,8 +71,10 @@ module Spoom
|
|
71
71
|
|
72
72
|
# Utils
|
73
73
|
|
74
|
-
|
75
|
-
|
74
|
+
class << self
|
75
|
+
def exit_on_failure?
|
76
|
+
true
|
77
|
+
end
|
76
78
|
end
|
77
79
|
end
|
78
80
|
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "fileutils"
|
5
|
+
require "open3"
|
6
|
+
|
7
|
+
module Spoom
|
8
|
+
# An abstraction to a Ruby project context
|
9
|
+
#
|
10
|
+
# A context maps to a directory in the file system.
|
11
|
+
# It is used to manipulate files and run commands in the context of this directory.
|
12
|
+
class Context
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
# The absolute path to the directory this context is about
|
16
|
+
sig { returns(String) }
|
17
|
+
attr_reader :absolute_path
|
18
|
+
|
19
|
+
class << self
|
20
|
+
extend T::Sig
|
21
|
+
|
22
|
+
# Create a new context in the system's temporary directory
|
23
|
+
#
|
24
|
+
# `name` is used as prefix to the temporary directory name.
|
25
|
+
# The directory will be created if it doesn't exist.
|
26
|
+
sig { params(name: T.nilable(String)).returns(T.attached_class) }
|
27
|
+
def mktmp!(name = nil)
|
28
|
+
new(::Dir.mktmpdir(name))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create a new context about `absolute_path`
|
33
|
+
#
|
34
|
+
# The directory will not be created if it doesn't exist.
|
35
|
+
# Call `#make!` to create it.
|
36
|
+
sig { params(absolute_path: String).void }
|
37
|
+
def initialize(absolute_path)
|
38
|
+
@absolute_path = T.let(::File.expand_path(absolute_path), String)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the absolute path to `relative_path` in the context's directory
|
42
|
+
sig { params(relative_path: String).returns(String) }
|
43
|
+
def absolute_path_to(relative_path)
|
44
|
+
File.join(@absolute_path, relative_path)
|
45
|
+
end
|
46
|
+
|
47
|
+
# File System
|
48
|
+
|
49
|
+
# Create the context directory at `absolute_path`
|
50
|
+
sig { void }
|
51
|
+
def mkdir!
|
52
|
+
FileUtils.rm_rf(@absolute_path)
|
53
|
+
FileUtils.mkdir_p(@absolute_path)
|
54
|
+
end
|
55
|
+
|
56
|
+
# List all files in this context matching `pattern`
|
57
|
+
sig { params(pattern: String).returns(T::Array[String]) }
|
58
|
+
def glob(pattern = "**/*")
|
59
|
+
Dir.glob(absolute_path_to(pattern)).map do |path|
|
60
|
+
Pathname.new(path).relative_path_from(@absolute_path).to_s
|
61
|
+
end.sort
|
62
|
+
end
|
63
|
+
|
64
|
+
# List all files at the top level of this context directory
|
65
|
+
sig { returns(T::Array[String]) }
|
66
|
+
def list
|
67
|
+
glob("*")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Does `relative_path` point to an existing file in this context directory?
|
71
|
+
sig { params(relative_path: String).returns(T::Boolean) }
|
72
|
+
def file?(relative_path)
|
73
|
+
File.file?(absolute_path_to(relative_path))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return the contents of the file at `relative_path` in this context directory
|
77
|
+
#
|
78
|
+
# Will raise if the file doesn't exist.
|
79
|
+
sig { params(relative_path: String).returns(String) }
|
80
|
+
def read(relative_path)
|
81
|
+
File.read(absolute_path_to(relative_path))
|
82
|
+
end
|
83
|
+
|
84
|
+
# Write `contents` in the file at `relative_path` in this context directory
|
85
|
+
#
|
86
|
+
# Append to the file if `append` is true.
|
87
|
+
sig { params(relative_path: String, contents: String, append: T::Boolean).void }
|
88
|
+
def write!(relative_path, contents = "", append: false)
|
89
|
+
absolute_path = absolute_path_to(relative_path)
|
90
|
+
FileUtils.mkdir_p(File.dirname(absolute_path))
|
91
|
+
File.write(absolute_path, contents, mode: append ? "a" : "w")
|
92
|
+
end
|
93
|
+
|
94
|
+
# Remove the path at `relative_path` (recursive + force) in this context directory
|
95
|
+
sig { params(relative_path: String).void }
|
96
|
+
def remove!(relative_path)
|
97
|
+
FileUtils.rm_rf(absolute_path_to(relative_path))
|
98
|
+
end
|
99
|
+
|
100
|
+
# Move the file or directory from `from_relative_path` to `to_relative_path`
|
101
|
+
sig { params(from_relative_path: String, to_relative_path: String).void }
|
102
|
+
def move!(from_relative_path, to_relative_path)
|
103
|
+
destination_path = absolute_path_to(to_relative_path)
|
104
|
+
FileUtils.mkdir_p(File.dirname(destination_path))
|
105
|
+
FileUtils.mv(absolute_path_to(from_relative_path), destination_path)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Delete this context and its content
|
109
|
+
#
|
110
|
+
# Warning: it will `rm -rf` the context directory on the file system.
|
111
|
+
sig { void }
|
112
|
+
def destroy!
|
113
|
+
FileUtils.rm_rf(@absolute_path)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Execution
|
117
|
+
|
118
|
+
# Run a command in this context directory
|
119
|
+
sig { params(command: String, capture_err: T::Boolean).returns(ExecResult) }
|
120
|
+
def exec(command, capture_err: true)
|
121
|
+
Bundler.with_unbundled_env do
|
122
|
+
opts = T.let({ chdir: @absolute_path }, T::Hash[Symbol, T.untyped])
|
123
|
+
|
124
|
+
if capture_err
|
125
|
+
out, err, status = Open3.capture3(command, opts)
|
126
|
+
ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus))
|
127
|
+
else
|
128
|
+
out, status = Open3.capture2(command, opts)
|
129
|
+
ExecResult.new(out: out, err: "", status: T.must(status.success?), exit_code: T.must(status.exitstatus))
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Bundle
|
135
|
+
|
136
|
+
# Read the `contents` of the Gemfile in this context directory
|
137
|
+
sig { returns(T.nilable(String)) }
|
138
|
+
def read_gemfile
|
139
|
+
read("Gemfile")
|
140
|
+
end
|
141
|
+
|
142
|
+
# Set the `contents` of the Gemfile in this context directory
|
143
|
+
sig { params(contents: String, append: T::Boolean).void }
|
144
|
+
def write_gemfile!(contents, append: false)
|
145
|
+
write!("Gemfile", contents, append: append)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Run a command with `bundle` in this context directory
|
149
|
+
sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
|
150
|
+
def bundle(command, version: nil)
|
151
|
+
command = "_#{version}_ #{command}" if version
|
152
|
+
exec("bundle #{command}")
|
153
|
+
end
|
154
|
+
|
155
|
+
# Run `bundle install` in this context directory
|
156
|
+
sig { params(version: T.nilable(String)).returns(ExecResult) }
|
157
|
+
def bundle_install!(version: nil)
|
158
|
+
bundle("install", version: version)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Run a command `bundle exec` in this context directory
|
162
|
+
sig { params(command: String, version: T.nilable(String)).returns(ExecResult) }
|
163
|
+
def bundle_exec(command, version: nil)
|
164
|
+
bundle("exec #{command}", version: version)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Git
|
168
|
+
|
169
|
+
# Run a command prefixed by `git` in this context directory
|
170
|
+
sig { params(command: String).returns(ExecResult) }
|
171
|
+
def git(command)
|
172
|
+
exec("git #{command}")
|
173
|
+
end
|
174
|
+
|
175
|
+
# Run `git init` in this context directory
|
176
|
+
sig { params(branch: String).void }
|
177
|
+
def git_init!(branch: "main")
|
178
|
+
git("init -q -b #{branch}")
|
179
|
+
end
|
180
|
+
|
181
|
+
# Run `git checkout` in this context directory
|
182
|
+
sig { params(ref: String).returns(ExecResult) }
|
183
|
+
def git_checkout!(ref: "main")
|
184
|
+
git("checkout #{ref}")
|
185
|
+
end
|
186
|
+
|
187
|
+
# Get the current git branch in this context directory
|
188
|
+
sig { returns(T.nilable(String)) }
|
189
|
+
def git_current_branch
|
190
|
+
Spoom::Git.current_branch(path: @absolute_path)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Sorbet
|
194
|
+
|
195
|
+
# Run `bundle exec srb` in this context directory
|
196
|
+
sig { params(command: String).returns(ExecResult) }
|
197
|
+
def srb(command)
|
198
|
+
bundle_exec("srb #{command}")
|
199
|
+
end
|
200
|
+
|
201
|
+
# Read the contents of `sorbet/config` in this context directory
|
202
|
+
sig { returns(String) }
|
203
|
+
def read_sorbet_config
|
204
|
+
read(Spoom::Sorbet::CONFIG_PATH)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Set the `contents` of `sorbet/config` in this context directory
|
208
|
+
sig { params(contents: String, append: T::Boolean).void }
|
209
|
+
def write_sorbet_config!(contents, append: false)
|
210
|
+
write!(Spoom::Sorbet::CONFIG_PATH, contents, append: append)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Read the strictness sigil from the file at `relative_path` (returns `nil` if no sigil)
|
214
|
+
sig { params(relative_path: String).returns(T.nilable(String)) }
|
215
|
+
def read_file_strictness(relative_path)
|
216
|
+
Spoom::Sorbet::Sigils.file_strictness(absolute_path_to(relative_path))
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -19,14 +19,18 @@ module Spoom
|
|
19
19
|
@data = data
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
22
|
+
class << self
|
23
|
+
extend T::Sig
|
24
|
+
|
25
|
+
sig { returns(String) }
|
26
|
+
def header_style
|
27
|
+
""
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { returns(String) }
|
31
|
+
def header_script
|
32
|
+
""
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
sig { returns(String) }
|