braid 1.1.9 → 1.1.10
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/lib/braid/command.rb +38 -15
- data/lib/braid/commands/add.rb +21 -11
- data/lib/braid/commands/diff.rb +27 -10
- data/lib/braid/commands/push.rb +27 -13
- data/lib/braid/commands/remove.rb +17 -4
- data/lib/braid/commands/setup.rb +12 -3
- data/lib/braid/commands/status.rb +17 -8
- data/lib/braid/commands/update.rb +47 -23
- data/lib/braid/commands/upgrade_config.rb +18 -4
- data/lib/braid/config.rb +28 -18
- data/lib/braid/main.rb +47 -33
- data/lib/braid/mirror.rb +40 -29
- data/lib/braid/operations.rb +10 -16
- data/lib/braid/operations_lite.rb +1 -1
- data/lib/braid/sorbet/fake_runtime.rb +19 -0
- data/lib/braid/version.rb +1 -1
- data/lib/braid.rb +2 -10
- metadata +49 -7
@@ -1,12 +1,26 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
module Braid
|
3
3
|
module Commands
|
4
4
|
class UpgradeConfig < Command
|
5
|
+
class Options < T::Struct
|
6
|
+
prop :dry_run, T::Boolean
|
7
|
+
prop :allow_breaking_changes, T::Boolean
|
8
|
+
end
|
9
|
+
|
10
|
+
sig {params(options: Options).void}
|
11
|
+
def initialize(options)
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
sig {returns(Config::ConfigMode)}
|
5
18
|
def config_mode
|
6
19
|
Config::MODE_UPGRADE
|
7
20
|
end
|
8
21
|
|
9
|
-
|
22
|
+
sig {void}
|
23
|
+
def run_internal
|
10
24
|
# Config loading in MODE_UPGRADE will bail out only if the config
|
11
25
|
# version is too new.
|
12
26
|
|
@@ -38,11 +52,11 @@ The following breaking changes will occur:
|
|
38
52
|
MSG
|
39
53
|
end
|
40
54
|
|
41
|
-
if options
|
55
|
+
if @options.dry_run
|
42
56
|
puts <<-MSG
|
43
57
|
Run 'braid upgrade-config#{config.breaking_change_descs.empty? ? '' : ' --allow-breaking-changes'}' to perform the upgrade.
|
44
58
|
MSG
|
45
|
-
elsif !config.breaking_change_descs.empty? &&
|
59
|
+
elsif !config.breaking_change_descs.empty? && !@options.allow_breaking_changes
|
46
60
|
raise BraidError, 'You must pass --allow-breaking-changes to accept the breaking changes.'
|
47
61
|
else
|
48
62
|
config.write_db
|
data/lib/braid/config.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
require 'yaml'
|
3
3
|
require 'json'
|
4
4
|
require 'yaml/store'
|
@@ -84,20 +84,29 @@ module Braid
|
|
84
84
|
sig {returns(T::Array[String])}
|
85
85
|
attr_reader :breaking_change_descs
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
@mode = options['mode'] || MODE_MAY_WRITE
|
87
|
+
sig {params(config_file: String, old_config_files: T::Array[String], mode: ConfigMode).void}
|
88
|
+
def initialize(config_file: CONFIG_FILE, old_config_files: [OLD_CONFIG_FILE], mode: MODE_MAY_WRITE)
|
89
|
+
@config_file = config_file
|
90
|
+
old_config_files = old_config_files
|
91
|
+
@mode = mode
|
93
92
|
|
94
93
|
data = load_config(@config_file, old_config_files)
|
95
|
-
@config_existed = !data.nil
|
94
|
+
@config_existed = T.let(!data.nil?, T::Boolean)
|
96
95
|
if !@config_existed
|
97
|
-
@config_version = CURRENT_CONFIG_VERSION
|
98
|
-
@db = {}
|
96
|
+
@config_version = T.let(CURRENT_CONFIG_VERSION, Integer)
|
97
|
+
@db = T.let({}, T::Hash[String, Mirror::MirrorAttributes])
|
99
98
|
elsif data['config_version'].is_a?(Numeric)
|
100
99
|
@config_version = data['config_version']
|
100
|
+
# WARNING (typing): In general, slapping a type annotation on the loaded
|
101
|
+
# data without validating its structure could be misleading as we work
|
102
|
+
# on the rest of the code. In the worst case, it might lead us to
|
103
|
+
# delete useful sanity checks because they trigger Sorbet unreachable
|
104
|
+
# code errors. As of this writing (2024-08-04), we have nothing to lose
|
105
|
+
# because we don't attempt to defend against malformed data anyway, and
|
106
|
+
# there's a benefit to having the type information available. If we add
|
107
|
+
# any validation, consider whether the annotations should be changed to
|
108
|
+
# verify that we did the validation correctly, e.g., by starting with a
|
109
|
+
# type like `T.anything` instead of `T.untyped`.
|
101
110
|
@db = data['mirrors']
|
102
111
|
else
|
103
112
|
# Before config versioning (Braid < 1.1.0)
|
@@ -115,7 +124,7 @@ MSG
|
|
115
124
|
end
|
116
125
|
|
117
126
|
# In all modes, instantiate all mirrors to scan for breaking changes.
|
118
|
-
@breaking_change_descs = []
|
127
|
+
@breaking_change_descs = T.let([], T::Array[String])
|
119
128
|
paths_to_delete = []
|
120
129
|
@db.each do |path, attributes|
|
121
130
|
begin
|
@@ -163,7 +172,7 @@ MSG
|
|
163
172
|
|
164
173
|
end
|
165
174
|
|
166
|
-
sig {params(url: String, options:
|
175
|
+
sig {params(url: String, options: Mirror::Options).returns(Mirror)}
|
167
176
|
def add_from_options(url, options)
|
168
177
|
mirror = Mirror.new_from_options(url, options)
|
169
178
|
|
@@ -171,7 +180,7 @@ MSG
|
|
171
180
|
mirror
|
172
181
|
end
|
173
182
|
|
174
|
-
sig {returns(T::Array[
|
183
|
+
sig {returns(T::Array[String])}
|
175
184
|
def mirrors
|
176
185
|
@db.keys
|
177
186
|
end
|
@@ -213,11 +222,12 @@ MSG
|
|
213
222
|
# Public for upgrade-config command only.
|
214
223
|
sig {void}
|
215
224
|
def write_db
|
216
|
-
new_db = {}
|
225
|
+
new_db = T.let({}, T::Hash[String, Mirror::MirrorAttributes])
|
217
226
|
@db.keys.sort.each do |key|
|
218
|
-
|
227
|
+
attrs = T.must(@db[key])
|
228
|
+
new_db[key] = new_attrs = {}
|
219
229
|
Braid::Mirror::ATTRIBUTES.each do |k|
|
220
|
-
|
230
|
+
new_attrs[k] = attrs[k] if attrs.has_key?(k)
|
221
231
|
end
|
222
232
|
end
|
223
233
|
new_data = {
|
@@ -237,7 +247,7 @@ MSG
|
|
237
247
|
(old_config_files + [config_file]).each do |file|
|
238
248
|
next unless File.exist?(file)
|
239
249
|
begin
|
240
|
-
store = T.
|
250
|
+
store = T.unsafe(YAML::Store).new(file)
|
241
251
|
data = {}
|
242
252
|
store.transaction(true) do
|
243
253
|
store.roots.each do |path|
|
@@ -258,7 +268,7 @@ MSG
|
|
258
268
|
@db[mirror.path] = clean_attributes(mirror.attributes)
|
259
269
|
end
|
260
270
|
|
261
|
-
sig {params(hash:
|
271
|
+
sig {params(hash: Mirror::MirrorAttributes).returns(Mirror::MirrorAttributes)}
|
262
272
|
def clean_attributes(hash)
|
263
273
|
hash.reject { |_, v| v.nil? }
|
264
274
|
end
|
data/lib/braid/main.rb
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
# `typed: strict` doesn't seem worth the trouble for this file at this time:
|
2
|
+
# there's not much for us to actually annotate, and it would take a crazy hack
|
3
|
+
# to avoid a Sorbet error on the `@argv` references because Sorbet doesn't seem
|
4
|
+
# to honor `T.bind` for instance variables (TODO: file a Sorbet bug about
|
5
|
+
# that?). Finding an approach to meaningfully check our code that uses the
|
6
|
+
# `main` DSL is a bigger project that we may or may not undertake later.
|
7
|
+
# ~ Matt 2024-08-04
|
8
|
+
#
|
1
9
|
# typed: true
|
2
10
|
|
3
11
|
require 'braid'
|
@@ -31,8 +39,8 @@ T.unsafe(Main).run {
|
|
31
39
|
# The "main" library doesn't provide a way to do this??
|
32
40
|
def check_no_extra_args!
|
33
41
|
if @argv.length > 0
|
34
|
-
|
35
|
-
|
42
|
+
Command.handle_error(
|
43
|
+
BraidError.new('Extra argument(s) passed to command.'))
|
36
44
|
end
|
37
45
|
end
|
38
46
|
|
@@ -61,7 +69,7 @@ T.unsafe(Main).run {
|
|
61
69
|
run {
|
62
70
|
check_no_extra_args!
|
63
71
|
Braid.verbose = verbose
|
64
|
-
|
72
|
+
Commands::Add.new(url, Mirror::Options.new(path: local_path, branch: branch, tag: tag, revision: revision, remote_path: path)).run
|
65
73
|
}
|
66
74
|
}
|
67
75
|
|
@@ -86,15 +94,15 @@ T.unsafe(Main).run {
|
|
86
94
|
|
87
95
|
run {
|
88
96
|
check_no_extra_args!
|
89
|
-
options =
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
97
|
+
options = Commands::Update::Options.new(
|
98
|
+
branch: branch,
|
99
|
+
tag: tag,
|
100
|
+
revision: revision,
|
101
|
+
head: head,
|
102
|
+
keep: keep
|
103
|
+
)
|
96
104
|
Braid.verbose = verbose
|
97
|
-
|
105
|
+
Commands::Update.new(local_path, options).run
|
98
106
|
}
|
99
107
|
}
|
100
108
|
|
@@ -115,11 +123,11 @@ T.unsafe(Main).run {
|
|
115
123
|
|
116
124
|
run {
|
117
125
|
check_no_extra_args!
|
118
|
-
options =
|
119
|
-
:
|
120
|
-
|
126
|
+
options = Commands::Remove::Options.new(
|
127
|
+
keep: keep
|
128
|
+
)
|
121
129
|
Braid.verbose = verbose
|
122
|
-
|
130
|
+
Commands::Remove.new(local_path, options).run
|
123
131
|
}
|
124
132
|
}
|
125
133
|
|
@@ -141,12 +149,12 @@ T.unsafe(Main).run {
|
|
141
149
|
if @argv.length > 0 && @argv[0] == '--'
|
142
150
|
@argv.shift
|
143
151
|
end
|
144
|
-
options =
|
145
|
-
|
146
|
-
|
147
|
-
|
152
|
+
options = Commands::Diff::Options.new(
|
153
|
+
keep: keep,
|
154
|
+
git_diff_args: @argv
|
155
|
+
)
|
148
156
|
Braid.verbose = verbose
|
149
|
-
|
157
|
+
Commands::Diff.new(local_path, options).run
|
150
158
|
}
|
151
159
|
}
|
152
160
|
|
@@ -159,12 +167,12 @@ T.unsafe(Main).run {
|
|
159
167
|
|
160
168
|
run {
|
161
169
|
check_no_extra_args!
|
162
|
-
options =
|
163
|
-
|
164
|
-
|
165
|
-
|
170
|
+
options = Commands::Push::Options.new(
|
171
|
+
keep: keep,
|
172
|
+
branch: branch
|
173
|
+
)
|
166
174
|
Braid.verbose = verbose
|
167
|
-
|
175
|
+
Commands::Push.new(local_path, options).run
|
168
176
|
}
|
169
177
|
}
|
170
178
|
|
@@ -179,7 +187,7 @@ T.unsafe(Main).run {
|
|
179
187
|
check_no_extra_args!
|
180
188
|
Braid.verbose = verbose
|
181
189
|
Braid.force = force
|
182
|
-
|
190
|
+
Commands::Setup.new(local_path).run
|
183
191
|
}
|
184
192
|
}
|
185
193
|
|
@@ -188,7 +196,7 @@ T.unsafe(Main).run {
|
|
188
196
|
|
189
197
|
run {
|
190
198
|
check_no_extra_args!
|
191
|
-
puts "braid #{
|
199
|
+
puts "braid #{VERSION}"
|
192
200
|
}
|
193
201
|
}
|
194
202
|
|
@@ -200,7 +208,7 @@ T.unsafe(Main).run {
|
|
200
208
|
run {
|
201
209
|
check_no_extra_args!
|
202
210
|
Braid.verbose = verbose
|
203
|
-
|
211
|
+
Commands::Status.new(local_path).run
|
204
212
|
}
|
205
213
|
}
|
206
214
|
|
@@ -220,6 +228,7 @@ T.unsafe(Main).run {
|
|
220
228
|
optional
|
221
229
|
desc 'Explain the consequences of the upgrade without performing it.'
|
222
230
|
attr :dry_run
|
231
|
+
default false
|
223
232
|
}
|
224
233
|
|
225
234
|
option('allow-breaking-changes') {
|
@@ -228,16 +237,17 @@ T.unsafe(Main).run {
|
|
228
237
|
Perform the upgrade even if it involves breaking changes.
|
229
238
|
DESC
|
230
239
|
attr :allow_breaking_changes
|
240
|
+
default false
|
231
241
|
}
|
232
242
|
|
233
243
|
run {
|
234
244
|
check_no_extra_args!
|
235
|
-
options =
|
236
|
-
|
237
|
-
|
238
|
-
|
245
|
+
options = Commands::UpgradeConfig::Options.new(
|
246
|
+
dry_run: dry_run,
|
247
|
+
allow_breaking_changes: allow_breaking_changes
|
248
|
+
)
|
239
249
|
Braid.verbose = verbose
|
240
|
-
|
250
|
+
Commands::UpgradeConfig.new(options).run
|
241
251
|
}
|
242
252
|
}
|
243
253
|
|
@@ -301,6 +311,7 @@ T.unsafe(Main).run {
|
|
301
311
|
optional
|
302
312
|
desc 'unused option'
|
303
313
|
attr
|
314
|
+
default false
|
304
315
|
}
|
305
316
|
}
|
306
317
|
|
@@ -309,6 +320,7 @@ T.unsafe(Main).run {
|
|
309
320
|
optional
|
310
321
|
desc 'log shell commands'
|
311
322
|
attr
|
323
|
+
default false
|
312
324
|
}
|
313
325
|
}
|
314
326
|
|
@@ -317,6 +329,7 @@ T.unsafe(Main).run {
|
|
317
329
|
optional
|
318
330
|
desc 'force'
|
319
331
|
attr
|
332
|
+
default false
|
320
333
|
}
|
321
334
|
}
|
322
335
|
|
@@ -325,6 +338,7 @@ T.unsafe(Main).run {
|
|
325
338
|
optional
|
326
339
|
desc 'do not remove the remote'
|
327
340
|
attr
|
341
|
+
default false
|
328
342
|
}
|
329
343
|
}
|
330
344
|
|
data/lib/braid/mirror.rb
CHANGED
@@ -14,12 +14,6 @@ module Braid
|
|
14
14
|
"unknown type: #{super}"
|
15
15
|
end
|
16
16
|
end
|
17
|
-
class PathRequired < BraidError
|
18
|
-
sig {returns(String)}
|
19
|
-
def message
|
20
|
-
'path is required'
|
21
|
-
end
|
22
|
-
end
|
23
17
|
class NoTagAndBranch < BraidError
|
24
18
|
sig {returns(String)}
|
25
19
|
def message
|
@@ -32,16 +26,21 @@ module Braid
|
|
32
26
|
sig {returns(String)}
|
33
27
|
attr_reader :path
|
34
28
|
|
35
|
-
# It's going to take significant refactoring to be able to give
|
36
|
-
|
29
|
+
# It's going to take significant refactoring to be able to give
|
30
|
+
# `MirrorAttributes` a type. Encapsulating the `T.untyped` in a type alias
|
31
|
+
# makes it easier to search for all the distinct root causes of untypedness
|
32
|
+
# in the code.
|
33
|
+
MirrorAttributes = T.type_alias { T::Hash[String, T.untyped] }
|
34
|
+
|
35
|
+
sig {returns(MirrorAttributes)}
|
37
36
|
attr_reader :attributes
|
38
37
|
|
39
38
|
BreakingChangeCallback = T.type_alias { T.proc.params(arg0: String).void }
|
40
39
|
|
41
|
-
sig {params(path: String, attributes:
|
40
|
+
sig {params(path: String, attributes: MirrorAttributes, breaking_change_cb: BreakingChangeCallback).void}
|
42
41
|
def initialize(path, attributes = {}, breaking_change_cb = DUMMY_BREAKING_CHANGE_CB)
|
43
42
|
@path = T.let(path.sub(/\/$/, ''), String)
|
44
|
-
@attributes = T.let(attributes.dup,
|
43
|
+
@attributes = T.let(attributes.dup, MirrorAttributes)
|
45
44
|
|
46
45
|
# Not that it's terribly important to check for such an old feature. This
|
47
46
|
# is mainly to demonstrate the RemoveMirrorDueToBreakingChange mechanism
|
@@ -79,19 +78,39 @@ DESC
|
|
79
78
|
@attributes.delete('squashed')
|
80
79
|
end
|
81
80
|
|
82
|
-
|
83
|
-
|
81
|
+
# `Mirror::Options` doubles as the options struct for the `add` command, so
|
82
|
+
# some properties are meaningful only in that context. TODO: Maybe the code
|
83
|
+
# could be organized in a better way.
|
84
|
+
class Options < T::Struct
|
85
|
+
prop :tag, T.nilable(String)
|
86
|
+
prop :branch, T.nilable(String)
|
87
|
+
# NOTE: Used only by the `add` command; ignored by
|
88
|
+
# `Mirror::new_from_options`.
|
89
|
+
prop :revision, T.nilable(Operations::Git::ObjectExpr)
|
90
|
+
prop :path, T.nilable(String)
|
91
|
+
prop :remote_path, T.nilable(String)
|
92
|
+
end
|
93
|
+
|
94
|
+
sig {params(url: String, options: Options).returns(Mirror)}
|
95
|
+
def self.new_from_options(url, options = Options.new)
|
84
96
|
url = url.sub(/\/$/, '')
|
97
|
+
# TODO: Ensure `url` is absolute? The user is probably more likely to
|
98
|
+
# move the downstream repository by itself than to move it along with the
|
99
|
+
# vendor repository. And we definitely don't want to use relative URLs in
|
100
|
+
# the cache.
|
85
101
|
|
86
|
-
raise NoTagAndBranch if options
|
102
|
+
raise NoTagAndBranch if options.tag && options.branch
|
87
103
|
|
88
|
-
tag = options
|
89
|
-
branch = options
|
104
|
+
tag = options.tag
|
105
|
+
branch = options.branch
|
90
106
|
|
91
|
-
path = (options
|
92
|
-
|
107
|
+
path = (options.path || extract_path_from_url(url, options.remote_path)).sub(/\/$/, '')
|
108
|
+
# TODO: Check that `path` is a valid relative path and not something like
|
109
|
+
# '.' or ''. Some of these pathological cases will cause Braid to bail
|
110
|
+
# out later when something else fails, but it would be better to check up
|
111
|
+
# front.
|
93
112
|
|
94
|
-
remote_path = options
|
113
|
+
remote_path = options.remote_path
|
95
114
|
|
96
115
|
attributes = {'url' => url, 'branch' => branch, 'path' => remote_path, 'tag' => tag}
|
97
116
|
self.new(path, attributes)
|
@@ -353,24 +372,16 @@ DESC
|
|
353
372
|
hash
|
354
373
|
end
|
355
374
|
|
356
|
-
|
357
|
-
sig {params(url: String, remote_path: T.nilable(String)).returns(T.nilable(String))}
|
375
|
+
sig {params(url: String, remote_path: T.nilable(String)).returns(String)}
|
358
376
|
def self.extract_path_from_url(url, remote_path)
|
359
377
|
if remote_path
|
360
378
|
return File.basename(remote_path)
|
361
379
|
end
|
362
380
|
|
363
|
-
# Avoid a Sorbet "unreachable code" error.
|
364
|
-
# TODO (typing): Fix this properly. Probably just remove this line?
|
365
|
-
return nil unless T.let(url, T.nilable(String))
|
366
381
|
name = File.basename(url)
|
367
382
|
|
368
|
-
|
369
|
-
|
370
|
-
name[0..-5]
|
371
|
-
else
|
372
|
-
name
|
373
|
-
end
|
383
|
+
# strip .git if present
|
384
|
+
name.delete_suffix('.git')
|
374
385
|
end
|
375
386
|
end
|
376
387
|
end
|
data/lib/braid/operations.rb
CHANGED
@@ -10,26 +10,18 @@ module Braid
|
|
10
10
|
|
11
11
|
module Operations
|
12
12
|
class ShellExecutionError < BraidError
|
13
|
-
|
14
|
-
sig {returns(T.nilable(String))}
|
13
|
+
sig {returns(String)}
|
15
14
|
attr_reader :err, :out
|
16
15
|
|
17
|
-
sig {params(err:
|
18
|
-
def initialize(err
|
16
|
+
sig {params(err: String, out: String).void}
|
17
|
+
def initialize(err, out)
|
19
18
|
@err = err
|
20
19
|
@out = out
|
21
20
|
end
|
22
21
|
|
23
22
|
sig {returns(String)}
|
24
23
|
def message
|
25
|
-
|
26
|
-
# Currently, first_line can be nil if @err was empty, but Sorbet thinks
|
27
|
-
# that the `message` method of an Exception should always return non-nil
|
28
|
-
# (although override checking isn't enforced as of this writing), so
|
29
|
-
# handle nil here. This seems ad-hoc but better than putting in a
|
30
|
-
# `T.must` that we know has a risk of being wrong. Hopefully this will
|
31
|
-
# be fixed better in https://github.com/cristibalan/braid/issues/90.
|
32
|
-
first_line.nil? ? '' : first_line
|
24
|
+
@err
|
33
25
|
end
|
34
26
|
end
|
35
27
|
class VersionTooLow < BraidError
|
@@ -201,7 +193,9 @@ module Braid
|
|
201
193
|
ObjectID = T.type_alias { String }
|
202
194
|
|
203
195
|
# A string containing an expression that can be evaluated to an object ID
|
204
|
-
# by `git rev-parse`.
|
196
|
+
# by `git rev-parse`. This type is enforced even less than `ObjectID` (in
|
197
|
+
# some cases, we apply it directly to user input without validation), but
|
198
|
+
# it still serves to document our intent.
|
205
199
|
ObjectExpr = T.type_alias { String }
|
206
200
|
|
207
201
|
# Get the physical path to a file in the git repository (e.g.,
|
@@ -252,7 +246,7 @@ module Braid
|
|
252
246
|
elsif out.match(/nothing.* to commit/)
|
253
247
|
false
|
254
248
|
else
|
255
|
-
raise ShellExecutionError,
|
249
|
+
raise ShellExecutionError.new(err, out)
|
256
250
|
end
|
257
251
|
end
|
258
252
|
|
@@ -364,7 +358,7 @@ module Braid
|
|
364
358
|
# This can happen if the user runs `braid add` with a `--path` that
|
365
359
|
# doesn't exist. TODO: Make the error message more user-friendly in
|
366
360
|
# that case.
|
367
|
-
raise
|
361
|
+
raise BraidError, 'No tree item exists at the given path'
|
368
362
|
end
|
369
363
|
mode = T.must(m[1])
|
370
364
|
type = T.must(m[2])
|
@@ -374,7 +368,7 @@ module Braid
|
|
374
368
|
elsif type == 'blob'
|
375
369
|
return BlobWithMode.new(hash, mode)
|
376
370
|
else
|
377
|
-
raise
|
371
|
+
raise BraidError, 'Tree item is not a tree or a blob'
|
378
372
|
end
|
379
373
|
end
|
380
374
|
end
|
@@ -22,7 +22,7 @@ module Braid
|
|
22
22
|
).returns(T.type_parameter(:R))
|
23
23
|
}
|
24
24
|
def self.with_modified_environment(dict, &blk)
|
25
|
-
orig_dict = {}
|
25
|
+
orig_dict = T.let({}, T::Hash[String, T.nilable(String)])
|
26
26
|
dict.each { |name, value|
|
27
27
|
orig_dict[name] = ENV[name]
|
28
28
|
ENV[name] = value
|
@@ -74,5 +74,24 @@ module Braid
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
+
class Struct
|
78
|
+
def initialize(**kwargs)
|
79
|
+
# The fake runtime isn't obliged to validate the property names or
|
80
|
+
# types.
|
81
|
+
#
|
82
|
+
# Note: If the caller passed a hash of keyword arguments, Ruby will copy
|
83
|
+
# it, so we don't need to copy `kwargs` again here to avoid aliasing.
|
84
|
+
@attrs = kwargs
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.prop(prop_name, prop_type)
|
88
|
+
define_method(prop_name) {
|
89
|
+
@attrs[prop_name]
|
90
|
+
}
|
91
|
+
define_method(:"#{prop_name}=") { |new_value|
|
92
|
+
@attrs[prop_name] = new_value
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
77
96
|
end
|
78
97
|
end
|
data/lib/braid/version.rb
CHANGED
data/lib/braid.rb
CHANGED
@@ -27,10 +27,7 @@ module Braid
|
|
27
27
|
!!@verbose
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
# apparently `lib/braid/main.rb` passes nil sometimes. Is that easy to fix?
|
32
|
-
# (Ditto with `self.force=` below.)
|
33
|
-
sig {params(new_value: T.nilable(T::Boolean)).void}
|
30
|
+
sig {params(new_value: T::Boolean).void}
|
34
31
|
def self.verbose=(new_value)
|
35
32
|
@verbose = !!new_value
|
36
33
|
end
|
@@ -42,7 +39,7 @@ module Braid
|
|
42
39
|
!!@force
|
43
40
|
end
|
44
41
|
|
45
|
-
sig {params(new_value: T
|
42
|
+
sig {params(new_value: T::Boolean).void}
|
46
43
|
def self.force=(new_value)
|
47
44
|
@force = !!new_value
|
48
45
|
end
|
@@ -59,11 +56,6 @@ module Braid
|
|
59
56
|
|
60
57
|
class BraidError < StandardError
|
61
58
|
extend T::Sig
|
62
|
-
sig {returns(String)}
|
63
|
-
def message
|
64
|
-
value = super
|
65
|
-
value if value != self.class.name
|
66
|
-
end
|
67
59
|
end
|
68
60
|
|
69
61
|
class InternalError < BraidError
|