gorails 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/Gemfile.lock +1 -1
  4. data/bin/update-deps +95 -0
  5. data/exe/gorails +3 -2
  6. data/lib/gorails/commands/railsbytes.rb +10 -10
  7. data/lib/gorails/commands.rb +1 -4
  8. data/lib/gorails/version.rb +1 -1
  9. data/lib/gorails.rb +11 -20
  10. data/vendor/deps/cli-kit/REVISION +1 -0
  11. data/vendor/deps/cli-kit/lib/cli/kit/args/definition.rb +301 -0
  12. data/vendor/deps/cli-kit/lib/cli/kit/args/evaluation.rb +237 -0
  13. data/vendor/deps/cli-kit/lib/cli/kit/args/parser/node.rb +131 -0
  14. data/vendor/deps/cli-kit/lib/cli/kit/args/parser.rb +128 -0
  15. data/vendor/deps/cli-kit/lib/cli/kit/args/tokenizer.rb +132 -0
  16. data/vendor/deps/cli-kit/lib/cli/kit/args.rb +15 -0
  17. data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +29 -0
  18. data/vendor/deps/cli-kit/lib/cli/kit/command_help.rb +256 -0
  19. data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +141 -0
  20. data/vendor/deps/cli-kit/lib/cli/kit/config.rb +137 -0
  21. data/vendor/deps/cli-kit/lib/cli/kit/core_ext.rb +30 -0
  22. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +165 -0
  23. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +99 -0
  24. data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +94 -0
  25. data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +89 -0
  26. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +95 -0
  27. data/vendor/deps/cli-kit/lib/cli/kit/opts.rb +284 -0
  28. data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +67 -0
  29. data/vendor/deps/cli-kit/lib/cli/kit/sorbet_runtime_stub.rb +142 -0
  30. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +253 -0
  31. data/vendor/deps/cli-kit/lib/cli/kit/support.rb +10 -0
  32. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +350 -0
  33. data/vendor/deps/cli-kit/lib/cli/kit/util.rb +133 -0
  34. data/vendor/deps/cli-kit/lib/cli/kit/version.rb +7 -0
  35. data/vendor/deps/cli-kit/lib/cli/kit.rb +151 -0
  36. data/vendor/deps/cli-ui/REVISION +1 -0
  37. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +180 -0
  38. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +98 -0
  39. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +216 -0
  40. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +116 -0
  41. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +176 -0
  42. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +149 -0
  43. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +112 -0
  44. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +300 -0
  45. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +92 -0
  46. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +58 -0
  47. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +72 -0
  48. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +102 -0
  49. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +534 -0
  50. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +36 -0
  51. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +354 -0
  52. data/vendor/deps/cli-ui/lib/cli/ui/sorbet_runtime_stub.rb +143 -0
  53. data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +46 -0
  54. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +292 -0
  55. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +82 -0
  56. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +264 -0
  57. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +53 -0
  58. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +107 -0
  59. data/vendor/deps/cli-ui/lib/cli/ui/version.rb +6 -0
  60. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +37 -0
  61. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +75 -0
  62. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +91 -0
  63. data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +63 -0
  64. data/vendor/deps/cli-ui/lib/cli/ui.rb +356 -0
  65. metadata +57 -1
@@ -0,0 +1,256 @@
1
+ # typed: true
2
+ require 'cli/kit'
3
+
4
+ module CLI
5
+ module Kit
6
+ module CommandHelp
7
+ extend T::Sig
8
+ include Kernel # for sorbet
9
+
10
+ sig { params(args: T::Array[String], name: String).void }
11
+ def call(args, name)
12
+ begin
13
+ defn = Args::Definition.new
14
+ opts = self.class.opts_class
15
+ opts.new(defn).install_to_definition
16
+ tokens = Args::Tokenizer.tokenize(args)
17
+ parse = Args::Parser.new(defn).parse(tokens)
18
+ result = Args::Evaluation.new(defn, parse)
19
+ opts_inst = opts.new(result)
20
+ rescue Args::Evaluation::TooManyPositions, Args::Evaluation::MissingRequiredPosition => e
21
+ STDERR.puts CLI::UI.fmt("{{red:{{bold:Error: #{e.message}}}}}")
22
+ STDERR.puts
23
+ STDERR.puts self.class.build_help
24
+ raise(AbortSilent)
25
+ rescue Args::Error => e
26
+ raise(Abort, e)
27
+ end
28
+
29
+ if opts_inst.helpflag
30
+ puts self.class.build_help
31
+ else
32
+ res = begin
33
+ opts.new(result)
34
+ rescue Args::Error => e
35
+ raise(Abort, e)
36
+ end
37
+ invoke_wrapper(res, name)
38
+ end
39
+ end
40
+
41
+ # use to implement error handling
42
+ sig { params(op: T.untyped, name: String).void }
43
+ def invoke_wrapper(op, name)
44
+ invoke(op, name)
45
+ end
46
+
47
+ sig { params(op: T.untyped, name: String).void }
48
+ def invoke(op, name)
49
+ raise(NotImplementedError, '#invoke must be implemented, or #call overridden')
50
+ end
51
+
52
+ sig { params(name: String).void }
53
+ def self.tool_name=(name)
54
+ @tool_name = name
55
+ end
56
+
57
+ sig { returns(String) }
58
+ def self._tool_name
59
+ unless @tool_name
60
+ raise 'You must set CLI::Kit::CommandHelp.tool_name='
61
+ end
62
+
63
+ @tool_name
64
+ end
65
+
66
+ module ClassMethods
67
+ extend T::Sig
68
+ include Kernel # for sorbet
69
+
70
+ DEFAULT_HELP_SECTIONS = [
71
+ :desc,
72
+ :long_desc,
73
+ :usage,
74
+ :examples,
75
+ :options,
76
+ ]
77
+
78
+ sig { returns(String) }
79
+ def build_help
80
+ h = (@help_sections || DEFAULT_HELP_SECTIONS).map do |section|
81
+ case section
82
+ when :desc
83
+ build_desc
84
+ when :long_desc
85
+ @long_desc
86
+ when :usage
87
+ @usage_section ||= build_usage
88
+ when :examples
89
+ @examples_section ||= build_examples
90
+ when :options
91
+ @options_section ||= build_options
92
+ else
93
+ raise "Unknown help section: #{section}"
94
+ end
95
+ end.compact.map(&:chomp).join("\n\n") + "\n"
96
+ CLI::UI.fmt(h)
97
+ end
98
+
99
+ sig { returns(String) }
100
+ def _command_name
101
+ return @command_name if @command_name
102
+
103
+ last_camel = send(:name).split('::').last
104
+ last_camel.gsub(/([a-z])([A-Z])/, '\1-\2').downcase
105
+ end
106
+
107
+ sig { returns(String) }
108
+ def _desc
109
+ @desc
110
+ end
111
+
112
+ sig { returns(String) }
113
+ def build_desc
114
+ out = +"{{command:#{CommandHelp._tool_name} #{_command_name}}}"
115
+ if @desc
116
+ out << ": #{@desc}"
117
+ end
118
+ "{{bold:#{out}}}"
119
+ end
120
+
121
+ sig { returns(T.untyped) }
122
+ def opts_class
123
+ T.unsafe(self).const_get(:Opts) # rubocop:disable Sorbet/ConstantsFromStrings
124
+ rescue NameError
125
+ Class.new(CLI::Kit::Opts)
126
+ end
127
+
128
+ sig { returns(T.nilable(String)) }
129
+ def build_options
130
+ opts = opts_class
131
+ return(nil) unless opts
132
+
133
+ methods = []
134
+ loop do
135
+ methods.concat(opts.public_instance_methods(false))
136
+ break if opts.superclass == CLI::Kit::Opts
137
+
138
+ opts = opts.superclass
139
+ end
140
+
141
+ @defn = Args::Definition.new
142
+ o = opts.new(@defn)
143
+ o.install_to_definition
144
+
145
+ return nil if @defn.options.empty? && @defn.flags.empty?
146
+
147
+ merged = T.let(@defn.options, T::Array[T.any(Args::Definition::Option, Args::Definition::Flag)])
148
+ merged += @defn.flags
149
+ merged.sort_by!(&:name)
150
+ "{{bold:Options:}}\n" + merged.map do |o|
151
+ if o.is_a?(Args::Definition::Option)
152
+ z = ' ' + [o.short&.prepend('-'), o.long&.prepend('--')].compact.join(', ') + ' VALUE'
153
+ default = if o.dynamic_default?
154
+ '(generated default)'
155
+ elsif o.default.nil?
156
+ '(no default)'
157
+ else
158
+ "(default: #{o.default.inspect})"
159
+ end
160
+ z << if o.desc
161
+ " {{italic:{{gray:# #{o.desc} #{default}}}}}"
162
+ else
163
+ " {{italic:{{gray:# #{default}}}}}"
164
+ end
165
+ else
166
+ z = ' ' + [o.short&.prepend('-'), o.long&.prepend('--')].compact.join(', ')
167
+ if o.desc
168
+ z << " {{italic:{{gray:# #{o.desc}}}}}"
169
+ end
170
+ end
171
+ z
172
+ end.join("\n")
173
+ end
174
+
175
+ sig { params(sections: T::Array[Symbol]).void }
176
+ def help_sections(sections)
177
+ @help_sections = sections
178
+ end
179
+
180
+ sig { params(command_name: String).void }
181
+ def command_name(command_name)
182
+ if @command_name
183
+ raise(ArgumentError, "Command name already set to #{@command_name}")
184
+ end
185
+
186
+ @command_name = command_name
187
+ end
188
+
189
+ sig { params(desc: String).void }
190
+ def desc(desc)
191
+ if desc.size > 80
192
+ raise(ArgumentError, 'description must be 80 characters or less')
193
+ end
194
+ if @desc
195
+ raise(ArgumentError, 'description already set')
196
+ end
197
+
198
+ @desc = desc
199
+ end
200
+
201
+ sig { params(long_desc: String).void }
202
+ def long_desc(long_desc)
203
+ if @long_desc
204
+ raise(ArgumentError, 'long description already set')
205
+ end
206
+
207
+ @long_desc = long_desc
208
+ end
209
+
210
+ sig { returns(String) }
211
+ def build_usage
212
+ '{{bold:Usage:}}' + case (@usage || []).size
213
+ when 0
214
+ " {{command:#{CommandHelp._tool_name} #{_command_name}}} [options]\n"
215
+ when 1
216
+ " {{command:#{CommandHelp._tool_name} #{_command_name}}} #{@usage.first}\n"
217
+ else
218
+ "\n" + @usage.map do |usage|
219
+ " {{command:#{CommandHelp._tool_name} #{_command_name}}} #{usage}\n"
220
+ end.join
221
+ end
222
+ end
223
+
224
+ sig { returns(T.nilable(String)) }
225
+ def build_examples
226
+ return nil unless @examples
227
+
228
+ cmd_prefix = " {{command:#{CommandHelp._tool_name} #{_command_name}}}"
229
+ "{{bold:Examples:}}\n" + @examples.map do |command, explanation|
230
+ cmd = "#{cmd_prefix} #{command}"
231
+ exp = "{{italic:{{gray:# #{explanation}}}}}"
232
+
233
+ width = CLI::UI::ANSI.printing_width(CLI::UI.fmt("#{cmd} #{exp}"))
234
+ if width > CLI::UI::Terminal.width
235
+ " #{exp}\n#{cmd}"
236
+ else
237
+ "#{cmd} #{exp}"
238
+ end
239
+ end.join("\n\n")
240
+ end
241
+
242
+ sig { params(usage: String).void }
243
+ def usage(usage)
244
+ @usage ||= []
245
+ @usage << usage
246
+ end
247
+
248
+ sig { params(command: String, explanation: T.nilable(String)).void }
249
+ def example(command, explanation)
250
+ @examples ||= []
251
+ @examples << [command, explanation]
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,141 @@
1
+ # typed: true
2
+ require 'cli/kit'
3
+
4
+ module CLI
5
+ module Kit
6
+ class CommandRegistry
7
+ extend T::Sig
8
+
9
+ CommandOrProc = T.type_alias do
10
+ T.any(T.class_of(CLI::Kit::BaseCommand), T.proc.returns(T.class_of(CLI::Kit::BaseCommand)))
11
+ end
12
+
13
+ sig { returns(T::Hash[String, CommandOrProc]) }
14
+ attr_reader :commands
15
+
16
+ sig { returns(T::Hash[String, String]) }
17
+ attr_reader :aliases
18
+
19
+ module ContextualResolver
20
+ extend T::Sig
21
+ extend T::Helpers
22
+ interface!
23
+
24
+ sig { abstract.returns(T::Array[String]) }
25
+ def command_names; end
26
+
27
+ sig { abstract.returns(T::Hash[String, String]) }
28
+ def aliases; end
29
+
30
+ sig { abstract.params(_name: String).returns(T.class_of(CLI::Kit::BaseCommand)) }
31
+ def command_class(_name); end
32
+ end
33
+
34
+ module NullContextualResolver
35
+ extend T::Sig
36
+ extend ContextualResolver
37
+
38
+ sig { override.returns(T::Array[String]) }
39
+ def self.command_names
40
+ []
41
+ end
42
+
43
+ sig { override.returns(T::Hash[String, String]) }
44
+ def self.aliases
45
+ {}
46
+ end
47
+
48
+ sig { override.params(_name: String).returns(T.class_of(CLI::Kit::BaseCommand)) }
49
+ def self.command_class(_name)
50
+ raise(CLI::Kit::Abort, 'Cannot be called on the NullContextualResolver since command_names is empty')
51
+ end
52
+ end
53
+
54
+ sig { params(default: String, contextual_resolver: ContextualResolver).void }
55
+ def initialize(default:, contextual_resolver: NullContextualResolver)
56
+ @commands = {}
57
+ @aliases = {}
58
+ @default = default
59
+ @contextual_resolver = contextual_resolver
60
+ end
61
+
62
+ sig { returns(T::Hash[String, T.class_of(CLI::Kit::BaseCommand)]) }
63
+ def resolved_commands
64
+ @commands.each_with_object({}) do |(k, v), a|
65
+ a[k] = resolve_class(v)
66
+ end
67
+ end
68
+
69
+ sig { params(const: CommandOrProc, name: String).void }
70
+ def add(const, name)
71
+ commands[name] = const
72
+ end
73
+
74
+ sig { params(name: T.nilable(String)).returns([T.nilable(T.class_of(CLI::Kit::BaseCommand)), String]) }
75
+ def lookup_command(name)
76
+ name = @default if name.to_s.empty?
77
+ resolve_command(T.must(name))
78
+ end
79
+
80
+ sig { params(from: String, to: String).void }
81
+ def add_alias(from, to)
82
+ aliases[from] = to unless aliases[from]
83
+ end
84
+
85
+ sig { returns(T::Array[String]) }
86
+ def command_names
87
+ @contextual_resolver.command_names + commands.keys
88
+ end
89
+
90
+ sig { params(name: String).returns(T::Boolean) }
91
+ def exist?(name)
92
+ !resolve_command(name).first.nil?
93
+ end
94
+
95
+ private
96
+
97
+ sig { params(name: String).returns(String) }
98
+ def resolve_alias(name)
99
+ aliases[name] || @contextual_resolver.aliases.fetch(name, name)
100
+ end
101
+
102
+ sig { params(name: String).returns([T.nilable(T.class_of(CLI::Kit::BaseCommand)), String]) }
103
+ def resolve_command(name)
104
+ name = resolve_alias(name)
105
+ resolve_global_command(name) || \
106
+ resolve_contextual_command(name) || \
107
+ [nil, name]
108
+ end
109
+
110
+ sig { params(name: String).returns(T.nilable([T.class_of(CLI::Kit::BaseCommand), String])) }
111
+ def resolve_global_command(name)
112
+ klass = resolve_class(commands.fetch(name, nil))
113
+ return nil unless klass
114
+
115
+ [klass, name]
116
+ rescue NameError
117
+ nil
118
+ end
119
+
120
+ sig { params(name: String).returns(T.nilable([T.class_of(CLI::Kit::BaseCommand), String])) }
121
+ def resolve_contextual_command(name)
122
+ found = @contextual_resolver.command_names.include?(name)
123
+ return nil unless found
124
+
125
+ [@contextual_resolver.command_class(name), name]
126
+ end
127
+
128
+ sig { params(class_or_proc: T.nilable(CommandOrProc)).returns(T.nilable(T.class_of(CLI::Kit::BaseCommand))) }
129
+ def resolve_class(class_or_proc)
130
+ case class_or_proc
131
+ when nil
132
+ nil
133
+ when Proc
134
+ class_or_proc.call
135
+ else
136
+ class_or_proc
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,137 @@
1
+ # typed: true
2
+ require 'cli/kit'
3
+ require 'fileutils'
4
+
5
+ module CLI
6
+ module Kit
7
+ class Config
8
+ extend T::Sig
9
+
10
+ XDG_CONFIG_HOME = 'XDG_CONFIG_HOME'
11
+
12
+ sig { params(tool_name: String).void }
13
+ def initialize(tool_name:)
14
+ @tool_name = tool_name
15
+ end
16
+
17
+ # Returns the config corresponding to `name` from the config file
18
+ # `false` is returned if it doesn't exist
19
+ #
20
+ # #### Parameters
21
+ # `section` : the section of the config value you are looking for
22
+ # `name` : the name of the config value you are looking for
23
+ #
24
+ # #### Returns
25
+ # `value` : the value of the config variable (nil if none)
26
+ #
27
+ # #### Example Usage
28
+ # `config.get('name.of.config')`
29
+ #
30
+ sig { params(section: String, name: String, default: T.nilable(String)).returns(T.nilable(String)) }
31
+ def get(section, name, default: nil)
32
+ all_configs.dig("[#{section}]", name) || default
33
+ end
34
+
35
+ # Coalesce and enforce the value of a config to a boolean
36
+ sig { params(section: String, name: String, default: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) }
37
+ def get_bool(section, name, default: false)
38
+ case get(section, name)
39
+ when 'true'
40
+ true
41
+ when 'false'
42
+ false
43
+ when nil
44
+ default
45
+ else
46
+ raise CLI::Kit::Abort, "Invalid config: #{section}.#{name} is expected to be true or false"
47
+ end
48
+ end
49
+
50
+ # Sets the config value in the config file
51
+ #
52
+ # #### Parameters
53
+ # `section` : the section of the config you are setting
54
+ # `name` : the name of the config you are setting
55
+ # `value` : the value of the config you are setting
56
+ #
57
+ # #### Example Usage
58
+ # `config.set('section', 'name.of.config', 'value')`
59
+ #
60
+ sig { params(section: String, name: String, value: T.nilable(T.any(String, T::Boolean))).void }
61
+ def set(section, name, value)
62
+ all_configs["[#{section}]"] ||= {}
63
+ case value
64
+ when nil
65
+ T.must(all_configs["[#{section}]"]).delete(name)
66
+ else
67
+ T.must(all_configs["[#{section}]"])[name] = value.to_s
68
+ end
69
+ write_config
70
+ end
71
+
72
+ # Unsets a config value in the config file
73
+ #
74
+ # #### Parameters
75
+ # `section` : the section of the config you are deleting
76
+ # `name` : the name of the config you are deleting
77
+ #
78
+ # #### Example Usage
79
+ # `config.unset('section', 'name.of.config')`
80
+ #
81
+ sig { params(section: String, name: String).void }
82
+ def unset(section, name)
83
+ set(section, name, nil)
84
+ end
85
+
86
+ # Gets the hash for the entire section
87
+ #
88
+ # #### Parameters
89
+ # `section` : the section of the config you are getting
90
+ #
91
+ # #### Example Usage
92
+ # `config.get_section('section')`
93
+ #
94
+ sig { params(section: String).returns(T::Hash[String, String]) }
95
+ def get_section(section)
96
+ (all_configs["[#{section}]"] || {}).dup
97
+ end
98
+
99
+ sig { returns(String) }
100
+ def to_s
101
+ ini.to_s
102
+ end
103
+
104
+ # The path on disk at which the configuration is stored:
105
+ # `$XDG_CONFIG_HOME/<toolname>/config`
106
+ # if ENV['XDG_CONFIG_HOME'] is not set, we default to ~/.config, e.g.:
107
+ # ~/.config/tool/config
108
+ #
109
+ sig { returns(String) }
110
+ def file
111
+ config_home = ENV.fetch(XDG_CONFIG_HOME, '~/.config')
112
+ File.expand_path(File.join(@tool_name, 'config'), config_home)
113
+ end
114
+
115
+ private
116
+
117
+ sig { returns(T::Hash[String, T::Hash[String, String]]) }
118
+ def all_configs
119
+ ini.ini
120
+ end
121
+
122
+ sig { returns(CLI::Kit::Ini) }
123
+ def ini
124
+ @ini ||= CLI::Kit::Ini.new(file).tap(&:parse)
125
+ end
126
+
127
+ sig { void }
128
+ def write_config
129
+ all_configs.each do |section, sub_config|
130
+ all_configs.delete(section) if sub_config.empty?
131
+ end
132
+ FileUtils.mkdir_p(File.dirname(file))
133
+ File.write(file, to_s)
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,30 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ class Exception
5
+ extend(T::Sig)
6
+
7
+ # You'd think instance variables @bug and @silent would work here. They
8
+ # don't. I'm not sure why. If you, the reader, want to take some time to
9
+ # figure it out, go ahead and refactor to that.
10
+
11
+ sig { returns(T::Boolean) }
12
+ def bug?
13
+ true
14
+ end
15
+
16
+ sig { returns(T::Boolean) }
17
+ def silent?
18
+ false
19
+ end
20
+
21
+ sig { params(bug: T::Boolean).void }
22
+ def bug!(bug = true)
23
+ singleton_class.define_method(:bug?) { bug }
24
+ end
25
+
26
+ sig { params(silent: T::Boolean).void }
27
+ def silent!(silent = true)
28
+ singleton_class.define_method(:silent?) { silent }
29
+ end
30
+ end