cli-ui 1.5.0 → 2.0.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.
data/lib/cli/ui.rb CHANGED
@@ -1,5 +1,13 @@
1
+ # typed: true
2
+
3
+ unless defined?(T)
4
+ require('cli/ui/sorbet_runtime_stub')
5
+ end
6
+
1
7
  module CLI
2
8
  module UI
9
+ extend T::Sig
10
+
3
11
  autoload :ANSI, 'cli/ui/ansi'
4
12
  autoload :Glyph, 'cli/ui/glyph'
5
13
  autoload :Color, 'cli/ui/color'
@@ -18,215 +26,344 @@ module CLI
18
26
  # Convenience accessor to +CLI::UI::Spinner::SpinGroup+
19
27
  SpinGroup = Spinner::SpinGroup
20
28
 
21
- # Glyph resolution using +CLI::UI::Glyph.lookup+
22
- # Look at the method signature for +Glyph.lookup+ for more details
23
- #
24
- # ==== Attributes
25
- #
26
- # * +handle+ - handle of the glyph to resolve
27
- #
28
- def self.glyph(handle)
29
- CLI::UI::Glyph.lookup(handle)
30
- end
29
+ Colorable = T.type_alias { T.any(Symbol, String, CLI::UI::Color) }
30
+ FrameStylable = T.type_alias { T.any(Symbol, String, CLI::UI::Frame::FrameStyle) }
31
+ IOLike = T.type_alias { T.any(IO, StringIO) }
32
+
33
+ class << self
34
+ extend T::Sig
31
35
 
32
- # Color resolution using +CLI::UI::Color.lookup+
33
- # Will lookup using +Color.lookup+ unless it's already a CLI::UI::Color (or nil)
34
- #
35
- # ==== Attributes
36
- #
37
- # * +input+ - color to resolve
38
- #
39
- def self.resolve_color(input)
40
- case input
41
- when CLI::UI::Color, nil
42
- input
43
- else
44
- CLI::UI::Color.lookup(input)
36
+ # Glyph resolution using +CLI::UI::Glyph.lookup+
37
+ # Look at the method signature for +Glyph.lookup+ for more details
38
+ #
39
+ # ==== Attributes
40
+ #
41
+ # * +handle+ - handle of the glyph to resolve
42
+ #
43
+ sig { params(handle: String).returns(Glyph) }
44
+ def glyph(handle)
45
+ CLI::UI::Glyph.lookup(handle)
45
46
  end
46
- end
47
47
 
48
- # Frame style resolution using +CLI::UI::Frame::FrameStyle.lookup+.
49
- # Will lookup using +FrameStyle.lookup+ unless it's already a CLI::UI::Frame::FrameStyle(or nil)
50
- #
51
- # ==== Attributes
52
- #
53
- # * +input+ - frame style to resolve
54
- def self.resolve_style(input)
55
- case input
56
- when CLI::UI::Frame::FrameStyle, nil
57
- input
58
- else
59
- CLI::UI::Frame::FrameStyle.lookup(input)
48
+ # Color resolution using +CLI::UI::Color.lookup+
49
+ # Will lookup using +Color.lookup+ unless it's already a CLI::UI::Color (or nil)
50
+ #
51
+ # ==== Attributes
52
+ #
53
+ # * +input+ - color to resolve
54
+ #
55
+ sig { params(input: Colorable).returns(CLI::UI::Color) }
56
+ def resolve_color(input)
57
+ case input
58
+ when CLI::UI::Color
59
+ input
60
+ else
61
+ CLI::UI::Color.lookup(input)
62
+ end
60
63
  end
61
- end
62
64
 
63
- # Convenience Method for +CLI::UI::Prompt.confirm+
64
- #
65
- # ==== Attributes
66
- #
67
- # * +question+ - question to confirm
68
- #
69
- def self.confirm(question, **kwargs)
70
- CLI::UI::Prompt.confirm(question, **kwargs)
71
- end
65
+ # Frame style resolution using +CLI::UI::Frame::FrameStyle.lookup+.
66
+ # Will lookup using +FrameStyle.lookup+ unless it's already a CLI::UI::Frame::FrameStyle(or nil)
67
+ #
68
+ # ==== Attributes
69
+ #
70
+ # * +input+ - frame style to resolve
71
+ sig { params(input: FrameStylable).returns(CLI::UI::Frame::FrameStyle) }
72
+ def resolve_style(input)
73
+ case input
74
+ when CLI::UI::Frame::FrameStyle
75
+ input
76
+ else
77
+ CLI::UI::Frame::FrameStyle.lookup(input.to_s)
78
+ end
79
+ end
72
80
 
73
- # Convenience Method for +CLI::UI::Prompt.ask+
74
- #
75
- # ==== Attributes
76
- #
77
- # * +question+ - question to ask
78
- # * +kwargs+ - arguments for +Prompt.ask+
79
- #
80
- def self.ask(question, **kwargs)
81
- CLI::UI::Prompt.ask(question, **kwargs)
82
- end
81
+ # Convenience Method for +CLI::UI::Prompt.confirm+
82
+ #
83
+ # ==== Attributes
84
+ #
85
+ # * +question+ - question to confirm
86
+ #
87
+ sig { params(question: String, default: T::Boolean).returns(T::Boolean) }
88
+ def confirm(question, default: true)
89
+ CLI::UI::Prompt.confirm(question, default: default)
90
+ end
83
91
 
84
- # Convenience Method to resolve text using +CLI::UI::Formatter.format+
85
- # Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
86
- #
87
- # ==== Attributes
88
- #
89
- # * +input+ - input to format
90
- # * +truncate_to+ - number of characters to truncate the string to (or nil)
91
- #
92
- def self.resolve_text(input, truncate_to: nil)
93
- return input if input.nil?
94
- formatted = CLI::UI::Formatter.new(input).format
95
- return formatted unless truncate_to
96
- CLI::UI::Truncater.call(formatted, truncate_to)
97
- end
92
+ # Convenience Method for +CLI::UI::Prompt.ask+
93
+ sig do
94
+ params(
95
+ question: String,
96
+ options: T.nilable(T::Array[String]),
97
+ default: T.nilable(T.any(String, T::Array[String])),
98
+ is_file: T::Boolean,
99
+ allow_empty: T::Boolean,
100
+ multiple: T::Boolean,
101
+ filter_ui: T::Boolean,
102
+ select_ui: T::Boolean,
103
+ options_proc: T.nilable(T.proc.params(handler: Prompt::OptionsHandler).void),
104
+ ).returns(T.any(String, T::Array[String]))
105
+ end
106
+ def ask(
107
+ question,
108
+ options: nil,
109
+ default: nil,
110
+ is_file: false,
111
+ allow_empty: true,
112
+ multiple: false,
113
+ filter_ui: true,
114
+ select_ui: true,
115
+ &options_proc
116
+ )
117
+ CLI::UI::Prompt.ask(
118
+ question,
119
+ options: options,
120
+ default: default,
121
+ is_file: is_file,
122
+ allow_empty: allow_empty,
123
+ multiple: multiple,
124
+ filter_ui: filter_ui,
125
+ select_ui: select_ui,
126
+ &options_proc
127
+ )
128
+ end
98
129
 
99
- # Convenience Method to format text using +CLI::UI::Formatter.format+
100
- # Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
101
- #
102
- # https://user-images.githubusercontent.com/3074765/33799827-6d0721a2-dd01-11e7-9ab5-c3d455264afe.png
103
- # https://user-images.githubusercontent.com/3074765/33799847-9ec03fd0-dd01-11e7-93f7-5f5cc540e61e.png
104
- #
105
- # ==== Attributes
106
- #
107
- # * +input+ - input to format
108
- #
109
- # ==== Options
110
- #
111
- # * +enable_color+ - should color be used? default to true unless output is redirected.
112
- #
113
- def self.fmt(input, enable_color: enable_color?)
114
- CLI::UI::Formatter.new(input).format(enable_color: enable_color)
115
- end
130
+ # Convenience Method to resolve text using +CLI::UI::Formatter.format+
131
+ # Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
132
+ #
133
+ # ==== Attributes
134
+ #
135
+ # * +input+ - input to format
136
+ # * +truncate_to+ - number of characters to truncate the string to (or nil)
137
+ #
138
+ sig { params(input: String, truncate_to: T.nilable(Integer)).returns(String) }
139
+ def resolve_text(input, truncate_to: nil)
140
+ formatted = CLI::UI::Formatter.new(input).format
141
+ return formatted unless truncate_to
116
142
 
117
- def self.wrap(input)
118
- CLI::UI::Wrap.new(input).wrap
119
- end
143
+ CLI::UI::Truncater.call(formatted, truncate_to)
144
+ end
120
145
 
121
- # Convenience Method for +CLI::UI::Printer.puts+
122
- #
123
- # ==== Attributes
124
- #
125
- # * +msg+ - Message to print
126
- # * +kwargs+ - keyword arguments for +Printer.puts+
127
- #
128
- def self.puts(msg, **kwargs)
129
- CLI::UI::Printer.puts(msg, **kwargs)
130
- end
146
+ # Convenience Method to format text using +CLI::UI::Formatter.format+
147
+ # Check +CLI::UI::Formatter::SGR_MAP+ for available formatting options
148
+ #
149
+ # https://user-images.githubusercontent.com/3074765/33799827-6d0721a2-dd01-11e7-9ab5-c3d455264afe.png
150
+ # https://user-images.githubusercontent.com/3074765/33799847-9ec03fd0-dd01-11e7-93f7-5f5cc540e61e.png
151
+ #
152
+ # ==== Attributes
153
+ #
154
+ # * +input+ - input to format
155
+ #
156
+ # ==== Options
157
+ #
158
+ # * +enable_color+ - should color be used? default to true unless output is redirected.
159
+ #
160
+ sig { params(input: String, enable_color: T::Boolean).returns(String) }
161
+ def fmt(input, enable_color: enable_color?)
162
+ CLI::UI::Formatter.new(input).format(enable_color: enable_color)
163
+ end
131
164
 
132
- # Convenience Method for +CLI::UI::Frame.open+
133
- #
134
- # ==== Attributes
135
- #
136
- # * +args+ - arguments for +Frame.open+
137
- # * +block+ - block for +Frame.open+
138
- #
139
- def self.frame(*args, **kwargs, &block)
140
- CLI::UI::Frame.open(*args, **kwargs, &block)
141
- end
165
+ sig { params(input: String).returns(String) }
166
+ def wrap(input)
167
+ CLI::UI::Wrap.new(input).wrap
168
+ end
142
169
 
143
- # Convenience Method for +CLI::UI::Spinner.spin+
144
- #
145
- # ==== Attributes
146
- #
147
- # * +args+ - arguments for +Spinner.open+
148
- # * +block+ - block for +Spinner.open+
149
- #
150
- def self.spinner(*args, **kwargs, &block)
151
- CLI::UI::Spinner.spin(*args, **kwargs, &block)
152
- end
170
+ # Convenience Method for +CLI::UI::Printer.puts+
171
+ #
172
+ # ==== Attributes
173
+ #
174
+ # * +msg+ - Message to print
175
+ # * +kwargs+ - keyword arguments for +Printer.puts+
176
+ #
177
+ sig do
178
+ params(
179
+ msg: String,
180
+ frame_color: T.nilable(Colorable),
181
+ to: IOLike,
182
+ encoding: Encoding,
183
+ format: T::Boolean,
184
+ graceful: T::Boolean,
185
+ wrap: T::Boolean,
186
+ ).void
187
+ end
188
+ def puts(
189
+ msg,
190
+ frame_color: nil,
191
+ to: $stdout,
192
+ encoding: Encoding::UTF_8,
193
+ format: true,
194
+ graceful: true,
195
+ wrap: true
196
+ )
197
+ CLI::UI::Printer.puts(
198
+ msg,
199
+ frame_color: frame_color,
200
+ to: to,
201
+ encoding: encoding,
202
+ format: format,
203
+ graceful: graceful,
204
+ wrap: wrap,
205
+ )
206
+ end
153
207
 
154
- # Convenience Method to override frame color using +CLI::UI::Frame.with_frame_color+
155
- #
156
- # ==== Attributes
157
- #
158
- # * +color+ - color to override to
159
- # * +block+ - block for +Frame.with_frame_color_override+
160
- #
161
- def self.with_frame_color(color, &block)
162
- CLI::UI::Frame.with_frame_color_override(color, &block)
163
- end
208
+ # Convenience Method for +CLI::UI::Frame.open+
209
+ #
210
+ # ==== Attributes
211
+ #
212
+ # * +args+ - arguments for +Frame.open+
213
+ # * +block+ - block for +Frame.open+
214
+ #
215
+ sig do
216
+ type_parameters(:T).params(
217
+ text: String,
218
+ color: T.nilable(Colorable),
219
+ failure_text: T.nilable(String),
220
+ success_text: T.nilable(String),
221
+ timing: T.any(T::Boolean, Numeric),
222
+ frame_style: FrameStylable,
223
+ block: T.nilable(T.proc.returns(T.type_parameter(:T))),
224
+ ).returns(T.nilable(T.type_parameter(:T)))
225
+ end
226
+ def frame(
227
+ text,
228
+ color: Frame::DEFAULT_FRAME_COLOR,
229
+ failure_text: nil,
230
+ success_text: nil,
231
+ timing: block_given?,
232
+ frame_style: Frame.frame_style,
233
+ &block
234
+ )
235
+ CLI::UI::Frame.open(
236
+ text,
237
+ color: color,
238
+ failure_text: failure_text,
239
+ success_text: success_text,
240
+ timing: timing,
241
+ frame_style: frame_style,
242
+ &block
243
+ )
244
+ end
164
245
 
165
- # Duplicate output to a file path
166
- #
167
- # ==== Attributes
168
- #
169
- # * +path+ - path to duplicate output to
170
- #
171
- def self.log_output_to(path)
172
- if CLI::UI::StdoutRouter.duplicate_output_to
173
- raise 'multiple logs not allowed'
174
- end
175
- CLI::UI::StdoutRouter.duplicate_output_to = File.open(path, 'w')
176
- yield
177
- ensure
178
- if (file_descriptor = CLI::UI::StdoutRouter.duplicate_output_to)
179
- file_descriptor.close
180
- CLI::UI::StdoutRouter.duplicate_output_to = nil
246
+ # Convenience Method for +CLI::UI::Spinner.spin+
247
+ #
248
+ # ==== Attributes
249
+ #
250
+ # * +args+ - arguments for +Spinner.open+
251
+ # * +block+ - block for +Spinner.open+
252
+ #
253
+ sig do
254
+ params(title: String, auto_debrief: T::Boolean, block: T.proc.params(task: Spinner::SpinGroup::Task).void)
255
+ .returns(T::Boolean)
256
+ end
257
+ def spinner(title, auto_debrief: true, &block)
258
+ CLI::UI::Spinner.spin(title, auto_debrief: auto_debrief, &block)
181
259
  end
182
- end
183
260
 
184
- # Disable all framing within a block
185
- #
186
- # ==== Attributes
187
- #
188
- # * +block+ - block in which to disable frames
189
- #
190
- def self.raw
191
- prev = Thread.current[:no_cliui_frame_inset]
192
- Thread.current[:no_cliui_frame_inset] = true
193
- yield
194
- ensure
195
- Thread.current[:no_cliui_frame_inset] = prev
196
- end
261
+ # Convenience Method to override frame color using +CLI::UI::Frame.with_frame_color+
262
+ #
263
+ # ==== Attributes
264
+ #
265
+ # * +color+ - color to override to
266
+ # * +block+ - block for +Frame.with_frame_color_override+
267
+ #
268
+ sig do
269
+ type_parameters(:T)
270
+ .params(color: Colorable, block: T.proc.returns(T.type_parameter(:T)))
271
+ .returns(T.type_parameter(:T))
272
+ end
273
+ def with_frame_color(color, &block)
274
+ CLI::UI::Frame.with_frame_color_override(color, &block)
275
+ end
197
276
 
198
- # Check whether colour is enabled in Formatter output. By default, colour
199
- # is enabled when STDOUT is a TTY; that is, when output has not been
200
- # redirected to another program or to a file.
201
- #
202
- def self.enable_color?
203
- @enable_color
204
- end
277
+ # Duplicate output to a file path
278
+ #
279
+ # ==== Attributes
280
+ #
281
+ # * +path+ - path to duplicate output to
282
+ #
283
+ sig do
284
+ type_parameters(:T)
285
+ .params(path: String, block: T.proc.returns(T.type_parameter(:T)))
286
+ .returns(T.type_parameter(:T))
287
+ end
288
+ def log_output_to(path, &block)
289
+ if CLI::UI::StdoutRouter.duplicate_output_to
290
+ raise 'multiple logs not allowed'
291
+ end
205
292
 
206
- # Turn colour output in Formatter on or off.
207
- #
208
- # ==== Attributes
209
- #
210
- # * +bool+ - true or false; enable or disable colour.
211
- #
212
- def self.enable_color=(bool)
213
- @enable_color = !!bool
214
- end
293
+ CLI::UI::StdoutRouter.duplicate_output_to = File.open(path, 'w')
294
+ yield
295
+ ensure
296
+ if (file_descriptor = CLI::UI::StdoutRouter.duplicate_output_to)
297
+ begin
298
+ file_descriptor.close
299
+ rescue IOError
300
+ nil
301
+ end
302
+ CLI::UI::StdoutRouter.duplicate_output_to = nil
303
+ end
304
+ end
215
305
 
216
- self.enable_color = $stdout.tty?
306
+ # Disable all framing within a block
307
+ #
308
+ # ==== Attributes
309
+ #
310
+ # * +block+ - block in which to disable frames
311
+ #
312
+ sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
313
+ def raw(&block)
314
+ prev = Thread.current[:no_cliui_frame_inset]
315
+ Thread.current[:no_cliui_frame_inset] = true
316
+ yield
317
+ ensure
318
+ Thread.current[:no_cliui_frame_inset] = prev
319
+ end
320
+
321
+ # Check whether colour is enabled in Formatter output. By default, colour
322
+ # is enabled when STDOUT is a TTY; that is, when output has not been
323
+ # redirected to another program or to a file.
324
+ #
325
+ sig { returns(T::Boolean) }
326
+ def enable_color?
327
+ @enable_color
328
+ end
329
+
330
+ # Turn colour output in Formatter on or off.
331
+ #
332
+ # ==== Attributes
333
+ #
334
+ # * +bool+ - true or false; enable or disable colour.
335
+ #
336
+ sig { params(bool: T::Boolean).void }
337
+ def enable_color=(bool)
338
+ @enable_color = !!bool
339
+ end
217
340
 
218
- # Set the default frame style.
219
- # Convenience method for setting the default frame style with +CLI::UI::Frame.frame_style=+
220
- #
221
- # Raises ArgumentError if +frame_style+ is not valid
222
- #
223
- # ==== Attributes
224
- #
225
- # * +symbol+ - the default frame style to use for frames
226
- #
227
- def self.frame_style=(frame_style)
228
- Frame.frame_style = frame_style.to_sym
341
+ # Set the default frame style.
342
+ # Convenience method for setting the default frame style with +CLI::UI::Frame.frame_style=+
343
+ #
344
+ # Raises ArgumentError if +frame_style+ is not valid
345
+ #
346
+ # ==== Attributes
347
+ #
348
+ # * +symbol+ - the default frame style to use for frames
349
+ #
350
+ sig { params(frame_style: FrameStylable).void }
351
+ def frame_style=(frame_style)
352
+ Frame.frame_style = frame_style
353
+ end
354
+
355
+ # Create a terminal link
356
+ sig { params(url: String, text: String, format: T::Boolean, blue_underline: T::Boolean).returns(String) }
357
+ def link(url, text, format: true, blue_underline: format)
358
+ raise 'cannot use blue_underline without format' if blue_underline && !format
359
+
360
+ text = "{{blue:{{underline:#{text}}}}}" if blue_underline
361
+ text = CLI::UI.fmt(text) if format
362
+ "\x1b]8;;#{url}\x1b\\#{text}\x1b]8;;\x1b\\"
363
+ end
229
364
  end
365
+
366
+ self.enable_color = $stdout.tty?
230
367
  end
231
368
  end
232
369
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cli-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
@@ -10,36 +10,36 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2021-04-14 00:00:00.000000000 Z
13
+ date: 2022-11-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: rake
16
+ name: minitest
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
19
  - - "~>"
20
20
  - !ruby/object:Gem::Version
21
- version: '13.0'
21
+ version: '5.0'
22
22
  type: :development
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
- version: '13.0'
28
+ version: '5.0'
29
29
  - !ruby/object:Gem::Dependency
30
- name: minitest
30
+ name: rake
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
33
  - - "~>"
34
34
  - !ruby/object:Gem::Version
35
- version: '5.0'
35
+ version: '13.0'
36
36
  type: :development
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
- version: '5.0'
42
+ version: '13.0'
43
43
  description: Terminal UI framework
44
44
  email:
45
45
  - burke.libbey@shopify.com
@@ -49,20 +49,8 @@ executables: []
49
49
  extensions: []
50
50
  extra_rdoc_files: []
51
51
  files:
52
- - ".dependabot/config.yml"
53
- - ".github/CODEOWNERS"
54
- - ".github/probots.yml"
55
- - ".gitignore"
56
- - ".rubocop.yml"
57
- - ".travis.yml"
58
- - Gemfile
59
- - Gemfile.lock
60
52
  - LICENSE.txt
61
53
  - README.md
62
- - Rakefile
63
- - bin/console
64
- - cli-ui.gemspec
65
- - dev.yml
66
54
  - lib/cli/ui.rb
67
55
  - lib/cli/ui/ansi.rb
68
56
  - lib/cli/ui/color.rb
@@ -79,6 +67,7 @@ files:
79
67
  - lib/cli/ui/prompt.rb
80
68
  - lib/cli/ui/prompt/interactive_options.rb
81
69
  - lib/cli/ui/prompt/options_handler.rb
70
+ - lib/cli/ui/sorbet_runtime_stub.rb
82
71
  - lib/cli/ui/spinner.rb
83
72
  - lib/cli/ui/spinner/async.rb
84
73
  - lib/cli/ui/spinner/spin_group.rb
@@ -109,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
98
  - !ruby/object:Gem::Version
110
99
  version: '0'
111
100
  requirements: []
112
- rubygems_version: 3.0.2
101
+ rubygems_version: 3.3.7
113
102
  signing_key:
114
103
  specification_version: 4
115
104
  summary: Terminal UI framework
@@ -1,8 +0,0 @@
1
- version: 1
2
- update_configs:
3
- - package_manager: "ruby:bundler"
4
- directory: "/"
5
- update_schedule: "weekly"
6
- automerged_updates:
7
- - match:
8
- update_type: "semver:minor"
data/.github/CODEOWNERS DELETED
@@ -1 +0,0 @@
1
- * @Shopify/dev-infra
data/.github/probots.yml DELETED
@@ -1,2 +0,0 @@
1
- enabled:
2
- - cla
data/.gitignore DELETED
@@ -1,14 +0,0 @@
1
- *.gem
2
- build
3
- .vagrant
4
- .DS_Store
5
- .bundle
6
-
7
- .starscope.db
8
- cscope.out
9
- tags
10
-
11
- .byebug_history
12
-
13
- .rubocop-*
14
- doc