natty-ui 0.35.0 → 1.0.3

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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -6
  3. data/examples/24bit-colors.rb +9 -5
  4. data/examples/3bit-colors.rb +7 -7
  5. data/examples/8bit-colors.rb +5 -7
  6. data/examples/attributes.rb +2 -3
  7. data/examples/elements.rb +9 -6
  8. data/examples/examples.rb +9 -9
  9. data/examples/frames.rb +31 -0
  10. data/examples/hbars.rb +6 -3
  11. data/examples/info.rb +13 -10
  12. data/examples/key-codes.rb +8 -9
  13. data/examples/ls.rb +24 -22
  14. data/examples/named-colors.rb +4 -3
  15. data/examples/sections.rb +26 -24
  16. data/examples/select.rb +28 -0
  17. data/examples/sh.rb +25 -7
  18. data/examples/tables.rb +19 -37
  19. data/examples/tasks.rb +32 -22
  20. data/examples/vbars.rb +5 -3
  21. data/lib/natty-ui/dumb_progress.rb +68 -0
  22. data/lib/natty-ui/element.rb +61 -70
  23. data/lib/natty-ui/features.rb +771 -944
  24. data/lib/natty-ui/frame.rb +87 -0
  25. data/lib/natty-ui/helper/table.rb +1376 -0
  26. data/lib/natty-ui/margin.rb +83 -0
  27. data/lib/natty-ui/progress.rb +116 -152
  28. data/lib/natty-ui/renderer/bars.rb +93 -0
  29. data/lib/natty-ui/renderer/choice.rb +56 -0
  30. data/lib/natty-ui/renderer/dumb_choice.rb +34 -0
  31. data/lib/natty-ui/renderer/dumb_select.rb +60 -0
  32. data/lib/natty-ui/renderer/dumb_shell_runner.rb +19 -0
  33. data/lib/natty-ui/renderer/heading.rb +26 -0
  34. data/lib/natty-ui/renderer/horizontal_rule.rb +32 -0
  35. data/lib/natty-ui/{ls_renderer.rb → renderer/ls.rb} +15 -27
  36. data/lib/natty-ui/renderer/mark.rb +13 -0
  37. data/lib/natty-ui/renderer/quote.rb +13 -0
  38. data/lib/natty-ui/renderer/select.rb +63 -0
  39. data/lib/natty-ui/renderer/shell.rb +15 -0
  40. data/lib/natty-ui/renderer/shell_runner.rb +29 -0
  41. data/lib/natty-ui/renderer/table_renderer.rb +446 -0
  42. data/lib/natty-ui/section.rb +144 -32
  43. data/lib/natty-ui/task.rb +38 -25
  44. data/lib/natty-ui/temporary.rb +27 -14
  45. data/lib/natty-ui/utils/border.rb +139 -0
  46. data/lib/natty-ui/utils/str_const.rb +61 -0
  47. data/lib/natty-ui/utils/utils.rb +47 -0
  48. data/lib/natty-ui/version.rb +1 -1
  49. data/lib/natty-ui.rb +86 -35
  50. metadata +31 -28
  51. data/examples/cols.rb +0 -38
  52. data/examples/illustration.rb +0 -60
  53. data/examples/options.rb +0 -28
  54. data/examples/themes.rb +0 -51
  55. data/lib/natty-ui/attributes.rb +0 -593
  56. data/lib/natty-ui/choice.rb +0 -67
  57. data/lib/natty-ui/dumb_choice.rb +0 -47
  58. data/lib/natty-ui/dumb_options.rb +0 -64
  59. data/lib/natty-ui/framed.rb +0 -50
  60. data/lib/natty-ui/hbars_renderer.rb +0 -66
  61. data/lib/natty-ui/options.rb +0 -78
  62. data/lib/natty-ui/shell_renderer.rb +0 -91
  63. data/lib/natty-ui/table.rb +0 -325
  64. data/lib/natty-ui/table_renderer.rb +0 -165
  65. data/lib/natty-ui/theme.rb +0 -403
  66. data/lib/natty-ui/utils.rb +0 -111
  67. data/lib/natty-ui/vbars_renderer.rb +0 -49
  68. data/lib/natty-ui/width_finder.rb +0 -137
  69. data/natty-ui.gemspec +0 -34
@@ -1,1134 +1,961 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NattyUI
4
- # These are all supported features by {NattyUI} or any other sub- element
5
- # like {#section}, {#message}, {#information}, {#warning}, {#error},
6
- # {#failed}, {#framed}, {#task}, ...
7
- #
8
- # Any printed text can contain *BBCode*-like embedded ANSI attributes which
9
- # will be used when the output terminal supports attributes and colors.
4
+ # Mixin that provides all UI methods to {NattyUI} and every {Element}.
10
5
  #
6
+ # You never include or extend this module directly. It is already available
7
+ # on {NattyUI} (via `extend`) and on every {Element} subclass (via
8
+ # `include`). The {Kernel#ui} helper always returns an object that responds
9
+ # to every method defined here.
11
10
  module Features
12
11
  #
13
- # @!group Printing Methods
14
- #
15
-
16
- # Print given text as lines.
17
- #
18
- # @example Print two lines text, right aligned
19
- # ui.puts "Two lines", "of nice text", align: :right
20
- # # => Two lines
21
- # # => of nice text
22
- #
23
- # @example Print two lines text, with a prefix
24
- # ui.puts "Two lines", "of nice text", prefix: ': '
25
- # # => : Two lines
26
- # # => : of nice text
27
- #
28
- # @see #pin
29
- #
30
- # @param text [*#to_s]
31
- # one or more objects to print line by line
32
- # @param options [{Symbol => Object}]
33
- # @option options [:left, :right, :centered] :align (:left)
34
- # text alignment
35
- # @option options [true, false] :eol (true)
36
- # whether to respect newline characters
37
- #
38
- # @return [Features]
39
- # itself
40
- def puts(*text, **options)
41
- if options.empty?
42
- bbcode = true
43
- max_width = Terminal.columns
12
+ # @!group Output methods
13
+ #
14
+
15
+ # Formats and prints text to the terminal.
16
+ #
17
+ # Text is word-wrapped to fit the available column width. BBCode-like
18
+ # markup is interpreted by default (e.g. `[b]bold[/b]`, `[red]text[/fg]`).
19
+ #
20
+ # @example Simple output with BBCode markup
21
+ # ui.puts 'Hello, [b]world[/b]!'
22
+ #
23
+ # @example Centred text with a prefix
24
+ # ui.puts 'Step 1 of 3', 'Connect to server',
25
+ # align: :center, prefix: '[cyan]→ '
26
+ #
27
+ # @example Suppress line breaks and compact spaces
28
+ # ui.puts <<~TEXT, eol: false, spaces: false
29
+ # Line one
30
+ # Line two here
31
+ # TEXT
32
+ # # => Line one Line two here
33
+ #
34
+ # @param text [#to_s, ...] one or more text values to print
35
+ # @param popts [Hash] a customizable set of print options
36
+ # @option popts [Boolean] :bbcode (true)
37
+ # interpret BBCode-like markup in the text
38
+ # @option popts [Boolean] :eol (true)
39
+ # when `false`, line breaks inside the text are collapsed to spaces
40
+ # @option popts [Boolean] :spaces (true)
41
+ # when `false`, runs of whitespace are compacted to a single space
42
+ # @option popts [:left, :center, :right, nil] :align
43
+ # horizontal text alignment within the available width
44
+ # @option popts [#to_s, nil] :prefix
45
+ # string prepended to the first output line
46
+ # @option popts [#to_s, nil] :suffix
47
+ # string appended to the last output line
48
+ # @option popts [#to_int, Float, nil] :max_width
49
+ # maximum line width in characters; `nil` uses the full terminal width;
50
+ # a negative `#to_int` value is an offset from the terminal width;
51
+ # a `Float` in `0.0..1.0` is a fraction of the terminal width
52
+ # @return [Features] itself
53
+ def puts(*text, **popts)
54
+ return if text.empty?
55
+
56
+ popts.delete(:pin) # ignore!
57
+
58
+ if popts.empty?
59
+ popts[:bbcode] = true
60
+ popts[:width] = max_width = columns
61
+ return self if max_width < 1
44
62
  else
45
- bbcode = true if (bbcode = options[:bbcode]).nil?
46
- ignore_newline = options[:eol] == false || options[:ignore_newline]
63
+ popts[:bbcode] = true unless popts.key?(:bbcode)
64
+ max_width = __determine_max_width(popts.delete(:max_width))
65
+ return self if max_width < 1
47
66
 
48
- if (max_width = options[:max_width]).nil?
49
- max_width = Terminal.columns
50
- elsif max_width < 1
51
- if max_width > 0
52
- max_width *= Terminal.columns
53
- elsif max_width < 0
54
- max_width += Terminal.columns
55
- else
56
- return self
57
- end
67
+ unless (prefix = popts.delete(:prefix)).nil?
68
+ prefix = StrConst[prefix] unless prefix.is_a?(StrConst)
69
+ return self if (max_width -= prefix.width) < 1
58
70
  end
59
71
 
60
- return self if max_width <= 0
61
-
62
- prefix_width =
63
- if (prefix = options[:prefix])
64
- prefix = Ansi.bbcode(prefix) if bbcode
65
- options[:prefix_width] || Text.width(prefix, bbcode: false)
66
- else
67
- 0
68
- end
69
-
70
- if (first_line = options[:first_line_prefix])
71
- first_line = Ansi.bbcode(first_line) if bbcode
72
- first_line_width =
73
- options[:first_line_prefix_width] ||
74
- Text.width(first_line, bbcode: false)
75
-
76
- if prefix_width < first_line_width
77
- prefix_next = "#{prefix}#{' ' * (first_line_width - prefix_width)}"
78
- prefix = first_line
79
- prefix_width = first_line_width
80
- else
81
- prefix_next = prefix
82
- prefix =
83
- if first_line_width < prefix_width
84
- first_line + (' ' * (prefix_width - first_line_width))
85
- else
86
- first_line
87
- end
72
+ if (cprefix = popts.delete(:cprefix)).nil?
73
+ cprefix = prefix
74
+ elsif cprefix == true
75
+ prefix, cprefix = StrConst.spacer(prefix.width), prefix if prefix
76
+ else
77
+ cprefix = StrConst[cprefix] unless cprefix.is_a?(StrConst)
78
+ unless prefix
79
+ return self if (max_width -= cprefix.width) < 1
80
+ prefix = StrConst.spacer(cprefix.width)
88
81
  end
89
82
  end
90
83
 
91
- max_width -= prefix_width
92
-
93
- if (suffix = options[:suffix])
94
- suffix = Ansi.bbcode(suffix) if bbcode
95
- max_width -=
96
- options[:suffix_width] || Text.width(suffix, bbcode: false)
84
+ unless (suffix = popts.delete(:suffix)).nil?
85
+ suffix = StrConst[suffix] unless suffix.is_a?(StrConst)
86
+ return self if (max_width -= suffix.width) < 1
97
87
  end
98
- end
99
-
100
- return self if max_width <= 0
101
-
102
- lines =
103
- Text.each_line_with_size(
104
- *text,
105
- limit: max_width,
106
- bbcode: bbcode,
107
- ansi: Terminal.ansi?,
108
- ignore_newline: ignore_newline
109
- )
110
- tail = options[:tail] and lines = lines.to_a.last(tail)
111
- @__eol ||= Terminal.ansi? ? "\e[m\n" : "\n"
112
88
 
113
- if (align = options[:align]).nil?
114
- lines.each do |line, _|
115
- Terminal.print(prefix, line, suffix, @__eol, bbcode: false)
116
- @lines_written += 1
117
- prefix, prefix_next = prefix_next, nil if prefix_next
89
+ if (csuffix = popts.delete(:csuffix)).nil?
90
+ csuffix = suffix
91
+ elsif csuffix == true
92
+ suffix, csuffix = StrConst.spacer(suffix.width), suffix if suffix
93
+ else
94
+ csuffix = StrConst[csuffix] unless csuffix.is_a?(StrConst)
95
+ return self if (max_width -= csuffix.width) < 1
96
+ suffix = StrConst.spacer(csuffix.width)
118
97
  end
119
- return self
120
- end
121
98
 
122
- unless options[:expand]
123
- max_width = (lines = lines.to_a).max_by(&:last)[-1]
99
+ popts[:width] = max_width
124
100
  end
125
101
 
126
- case align
127
- when :right
128
- lines.each do |line, width|
129
- Terminal.print(
130
- prefix,
131
- ' ' * (max_width - width),
132
- line,
133
- suffix,
134
- @__eol,
135
- bbcode: false
136
- )
137
- @lines_written += 1
138
- prefix, prefix_next = prefix_next, nil if prefix_next
139
- end
140
- when :centered
141
- lines.each do |line, width|
142
- space = max_width - width
143
- Terminal.print(
144
- prefix,
145
- ' ' * (lw = space / 2),
146
- line,
147
- ' ' * (space - lw),
148
- suffix,
149
- @__eol,
150
- bbcode: false
151
- )
152
- @lines_written += 1
153
- prefix, prefix_next = prefix_next, nil if prefix_next
154
- end
155
- else
156
- lines.each do |line, width|
157
- Terminal.print(
158
- prefix,
159
- line,
160
- ' ' * (max_width - width),
161
- suffix,
162
- @__eol,
163
- bbcode: false
164
- )
165
- @lines_written += 1
166
- prefix, prefix_next = prefix_next, nil if prefix_next
102
+ popts[:ansi] = Terminal.ansi?
103
+ lines = Text.format(*text, **popts)
104
+
105
+ if cprefix || csuffix
106
+ lines.map! do |line|
107
+ line = "#{cprefix}#{line}#{csuffix}"
108
+ cprefix, prefix = prefix, nil if prefix
109
+ csuffix, suffix = suffix, nil if suffix
110
+ line
167
111
  end
168
112
  end
113
+ Terminal.puts(*lines, bbcode: false)
114
+ @lines_written += lines.size
169
115
  self
170
116
  end
171
117
 
172
- # Print given text with a decoration mark.
173
- #
174
- # @param text (see puts)
175
- # @param mark [Symbol, #to_s]
176
- # marker type
177
- #
178
- # @return (see puts)
179
- def mark(*text, mark: :default, **options)
180
- mark = Theme.current.mark(mark)
181
- options[:first_line_prefix] = mark
182
- options[:first_line_prefix_width] = mark.width
183
- puts(*text, **options)
118
+ # Prints one or more blank lines.
119
+ #
120
+ # @example Print a single blank line
121
+ # ui.space
122
+ #
123
+ # @example Print three blank lines
124
+ # ui.space 3
125
+ #
126
+ # @param count [#to_int] number of blank lines to print
127
+ # @return (see #puts)
128
+ def space(count = 1)
129
+ (count = count.to_int) < 1 ? self : puts(" \n" * count)
184
130
  end
185
131
 
186
- # Print given text as lines like {#puts}. Used in elements with temporary
187
- # output like {#task} the text will be kept ("pinned").
132
+ # Prints text with a leading mark symbol.
188
133
  #
189
- # It can optionally have a decoration marker in first line like {#mark}.
134
+ # @example Default mark
135
+ # ui.mark 'Item one'
190
136
  #
191
- # @example Print two lines decorated as information which are pinned
192
- # ui.task 'Do something important' do |task|
193
- # # ...
194
- # task.pin("This is text", "which is pinned", mark: :information)
195
- # # ...
196
- # end
197
- # # => ✓ Do something important
198
- # # => 𝒊 This is text
199
- # # => which is pinned.
137
+ # @example Named symbol mark
138
+ # ui.mark 'Done!', mark: :checkmark
200
139
  #
201
- # @param (see #puts)
202
- # @param mark (see #mark)
203
- # @option (see #puts)
140
+ # @example Custom string mark
141
+ # ui.mark 'Warning', mark: '[bright_red]!'
204
142
  #
205
- # @return (see puts)
206
- def pin(*text, mark: nil, **options)
207
- mark(*text, mark: mark, pin: true, **options)
143
+ # @param text (see #puts)
144
+ # @param mark [Symbol, #to_s] mark to use as prefix:
145
+ # - `:default` blue bullet
146
+ # - `:checkmark` — green check mark ✓
147
+ # - `:item` — dim bullet •
148
+ # - any other object — converted via `#to_s` and used as the literal prefix
149
+ # @param popts [Hash] a customizable set of print options, see {#puts}
150
+ # @return (see #puts)
151
+ def mark(*text, mark: :default, **popts)
152
+ Mark.render(self, mark, *text, **popts)
208
153
  end
209
154
 
210
- # Print given text as a quotation.
155
+ # Prints text with a green check-mark prefix.
211
156
  #
212
- # @param text (see puts)
157
+ # Shorthand for `mark(*text, mark: :checkmark, **options)`.
213
158
  #
214
- # @return (see puts)
215
- def quote(*text)
216
- width = columns * 0.75
217
- quote = Theme.current.mark(:quote)
218
- puts(
219
- *text,
220
- prefix: quote,
221
- prefix_width: quote.width,
222
- max_width: width < 20 ? nil : width.round
223
- )
224
- end
225
-
226
- # Print given text as a heading.
159
+ # @example
160
+ # ui.ok 'All tests passed'
227
161
  #
228
- # There are specific shortcuts for heading levels:
229
- # {#h1}, {#h2}, {#h3}, {#h4}, {#h5}, {#h6}.
162
+ # @param text (see #puts)
163
+ # @param popts (see #mark)
164
+ # @return (see #puts)
165
+ def ok(*text, **popts) = mark(*text, mark: :checkmark, **popts)
166
+
167
+ # Prints text with a mark that persists after a {Temporary} element closes.
230
168
  #
231
- # @example Print a level 1 heading
232
- # ui.heading(1, 'This is a H1 heading element')
233
- # # => ╴╶╴╶─═══ This is a H1 heading element ═══─╴╶╴╶
169
+ # Identical to {#mark} but survives when the surrounding {Temporary} element
170
+ # (see {#temporary}, {#task}, {#progress}) is erased.
234
171
  #
235
- # @param level [#to_i]
236
- # heading level, one of 1..6
237
- # @param text (see puts)
172
+ # @example Pin a success note inside a task
173
+ # ui.task 'Deploy' do
174
+ # do_deploy
175
+ # ui.pin 'Deployed to production', mark: :checkmark
176
+ # end
238
177
  #
239
- # @return (see puts)
240
- def heading(level, *text)
241
- prefix, suffix = Theme.current.heading(level)
242
- puts(
243
- *text,
244
- max_width: columns,
245
- prefix: prefix,
246
- prefix_width: prefix.width,
247
- suffix: suffix,
248
- suffix_width: suffix.width,
249
- align: :centered
250
- )
178
+ # @param (see mark)
179
+ # @return (see #puts)
180
+ def pin(*text, mark: :default, **popts)
181
+ mark(*text, mark:, pin: true, **popts)
251
182
  end
252
183
 
253
- # Print given text as a H1 {#heading}.
184
+ # Prints text with a left-side quotation border.
254
185
  #
255
- # @param text (see puts)
186
+ # @example
187
+ # ui.quote "To be or not to be,\nthat is the question."
256
188
  #
257
- # @return (see puts)
258
- def h1(*text) = heading(1, *text)
189
+ # @param (see #puts)
190
+ # @return (see #puts)
191
+ def quote(*text) = Quote.render(self, *text)
259
192
 
260
- # Print given text as a H2 {#heading}.
193
+ # Prints a heading at the given level.
261
194
  #
262
- # @param text (see puts)
195
+ # Six heading levels are supported (similarly to HTML `<h1>`–`<h6>`).
196
+ # Use the shorthand helpers {#h1}–{#h6} for convenience.
263
197
  #
264
- # @return (see puts)
265
- def h2(*text) = heading(2, *text)
266
-
267
- # Print given text as a H3 {#heading}.
198
+ # @example Large heading
199
+ # ui.heading 1, 'Chapter One'
268
200
  #
269
- # @param text (see puts)
201
+ # @example Sub-heading
202
+ # ui.heading 3, 'Section [i]Overview[/i]'
270
203
  #
271
- # @return (see puts)
272
- def h3(*text) = heading(3, *text)
204
+ # @param level [#to_int] heading level, `1` (largest) to `6` (smallest)
205
+ # @param title [#to_s] heading text
206
+ # @return (see #puts)
207
+ def heading(level, title) = Heading.render(self, level, title)
273
208
 
274
- # Print given text as a H4 {#heading}.
209
+ # Prints a level-1 heading.
275
210
  #
276
- # @param text (see puts)
277
- #
278
- # @return (see puts)
279
- def h4(*text) = heading(4, *text)
211
+ # @param title (see #heading)
212
+ # @return (see #puts)
213
+ def h1(title) = heading(1, title)
280
214
 
281
- # Print given text as a H5 {#heading}.
282
- #
283
- # @param text (see puts)
215
+ # Prints a level-2 heading.
284
216
  #
285
- # @return (see puts)
286
- def h5(*text) = heading(5, *text)
217
+ # @param title (see #heading)
218
+ # @return (see #puts)
219
+ def h2(title) = heading(2, title)
287
220
 
288
- # Print given text as a H6 {#heading}.
289
- #
290
- # @param text (see puts)
221
+ # Prints a level-3 heading.
291
222
  #
292
- # @return (see puts)
293
- def h6(*text) = heading(6, *text)
223
+ # @param title (see #heading)
224
+ # @return (see #puts)
225
+ def h3(title) = heading(3, title)
294
226
 
295
- # Print a horizontal rule.
296
- #
297
- # @example Print double line
298
- # ui.hr :double
299
- #
300
- # @param type [Symbol]
301
- # border type
227
+ # Prints a level-4 heading.
302
228
  #
303
- # @return (see puts)
304
- def hr(type = :default)
305
- theme = Theme.current
306
- bc = theme.border(type)[10]
307
- puts("#{theme.heading_sytle}#{bc * columns}")
308
- end
229
+ # @param title (see #heading)
230
+ # @return (see #puts)
231
+ def h4(title) = heading(4, title)
309
232
 
310
- # Print one or more space lines.
311
- #
312
- # @param count [#to_i]
313
- # lines to print
233
+ # Prints a level-5 heading.
314
234
  #
315
- # @return (see puts)
316
- def space(count = 1)
317
- (count = count.to_i).positive? ? puts("\n" * count) : self
318
- end
235
+ # @param title (see #heading)
236
+ # @return (see #puts)
237
+ def h5(title) = heading(5, title)
319
238
 
320
- # Print given items as list (like 'ls' command).
239
+ # Prints a level-6 heading.
321
240
  #
322
- # Each list item will optionally be decorated with the given glyph as:
323
- #
324
- # - `Integer` as the start value for a numbered list
325
- # - `Symbol` as the start symbol
326
- # - `:hex` to create a hexadecimal numbered list
327
- # - any text as prefix
328
- #
329
- # @example Print all Ruby files as a numbered list
330
- # ui.ls Dir['*/**/*.rb'], glyph: 1
241
+ # @param title (see #heading)
242
+ # @return (see #puts)
243
+ def h6(title) = heading(6, title)
244
+
245
+ # Prints a horizontal rule spanning the available width.
331
246
  #
332
- # @example Print all Ruby files as a bullet point list (with green bullets)
333
- # ui.ls Dir['*/**/*.rb'], glyph: '[green]•[/fg]'
247
+ # @example Default rule
248
+ # ui.hr
334
249
  #
335
- # @param items [#to_s]
336
- # one or more convertible objects to list
337
- # @param compact [true, false]
338
- # whether the compact display format should be used
339
- # @param glyph [Integer, :hex, Symbol, #to_s]
340
- # glyph to be used as prefix
250
+ # @example Named style
251
+ # ui.hr :double
341
252
  #
342
- # @return (see puts)
253
+ # @example Repeated string
254
+ # ui.hr '+-'
255
+ #
256
+ # @param kind [Symbol, #to_s, nil]
257
+ # - `nil` — default style
258
+ # - `Symbol` — named style: `:single`, `:double`, `:heavy`
259
+ # - `#to_s` — string repeated to fill the available width
260
+ # @return (see #puts)
261
+ def hr(kind = nil) = HorizontalRule.render(self, kind)
262
+
263
+ # Prints a multi-column list.
264
+ #
265
+ # Items are arranged in columns to fit the terminal width.
266
+ #
267
+ # @example Plain list
268
+ # ui.ls 'Alice', 'Bob', 'Carol'
269
+ #
270
+ # @example Hex-numbered list
271
+ # ui.ls 'red', 'green', 'blue', glyph: :hex
272
+ #
273
+ # @param items [#to_s, ...] items to list; nested arrays are flattened
274
+ # @param compact [Boolean] `true` = ordered in columns,
275
+ # `false` = ordered left to right per row
276
+ # @param glyph prefix applied to every item:
277
+ # - `nil` / `false` — no prefix
278
+ # - `Integer` — decimal counter starting at the given number
279
+ # - `Symbol` — alphabetic sequence starting at the given symbol
280
+ # (e.g. `:a` → `a`, `b`, `c`, …)
281
+ # - `:hex` — zero-based hex counter (`01`, `02`, …)
282
+ # - a `#to_s` value matching a hex value — hex counter at the given
283
+ # offset (e.g. `"0x0a"` starts at `0a`)
284
+ # - any other `#to_s` value — used as a literal prefix for every item
285
+ # @return (see #puts)
343
286
  def ls(*items, compact: true, glyph: nil)
344
287
  return self if items.empty?
345
- renderer = compact ? CompactLSRenderer : LSRenderer
346
- puts(*renderer.lines(items, glyph, columns))
288
+ puts(*(compact ? CompactLS : LS).lines(columns, items, glyph))
347
289
  end
348
290
 
349
- # Generate and print a table.
350
- # See {Table} for much more details about table generation.
351
- #
352
- # @example Draw a very simple 3x4 table with complete borders
353
- # ui.table(border: :default, border_around: true, padding: [0, 1]) do |table|
354
- # table.add 1, 2, 3, 4
355
- # table.add 5, 6, 7, 8
356
- # table.add 9, 10, 11, 12
357
- # end
358
- #
359
- # @param attributes [{Symbol => Object}]
360
- # attributes for the table and default attributes for table cells
361
- # @option attributes [Symbol] :border (nil)
362
- # kind of border,
363
- # see {Table::Attributes}
364
- # @option attributes [Enumerable<Symbol>] :border_style (nil)
365
- # style of border,
366
- # see {Table::Attributes}
367
- # @option attributes [true, false] :border_around (false)
368
- # whether the table should have a border around,
369
- # see {Table::Attributes}
370
- # @option attributes [:left, :right, :centered] :position (false)
371
- # where to align the table,
372
- # see {Table::Attributes}
373
- #
374
- # @yieldparam table [Table]
375
- # helper to define the table layout
376
- #
377
- # @return (see puts)
378
- def table(**attributes)
379
- return self unless block_given?
380
- yield(table = Table.new(**attributes))
381
- puts(
382
- *TableRenderer[table, columns],
383
- align: table.attributes.position,
384
- expand: true
385
- )
386
- end
387
-
388
- # Print text in columns.
389
- # This is a shorthand to define a {Table} with a single row.
390
- #
391
- # @param columns [#to_s]
392
- # two or more convertible objects to print side by side
393
- # @param attributes (see table)
394
- # @option attributes (see table)
395
- # @option attributes [Integer] :width (nil)
396
- # width of a column,
397
- # see {Attributes::Width}
398
- #
399
- # @yieldparam row [Table::Row]
400
- # helper to define the row layout
401
- #
402
- # @return (see puts)
403
- def cols(*columns, **attributes)
404
- tab_att, att = Utils.split_table_attr(attributes)
405
- table(**tab_att) do |table|
406
- table.add do |row|
407
- columns.each { row.add(_1, **att) }
408
- yield(row) if block_given?
409
- end
410
- end
411
- end
412
-
413
- # Print a text division with attributes.
414
- # This is a shorthand to define a {Table} with a single cell.
415
- #
416
- # @param text (see puts)
417
- # @param attributes [{Symbol => Object}]
418
- # attributes for the division
419
- # @option attributes [:left, :right, :centered] :align (:left)
420
- # text alignment,
421
- # see {Attributes::Align}
422
- # @option attributes [Integer, Enumerable<Integer>] :padding (nil)
423
- # text padding,
424
- # see {Attributes::Padding}
425
- # @option attributes [Enumerable<Symbol>] :style (nil)
426
- # text style,
427
- # see {Attributes::Style}
428
- # @option attributes [Integer] :width (nil)
429
- # width of the cell,
430
- # see {Attributes::Width}
431
- # @option attributes (see table)
432
- #
433
- # @return (see puts)
434
- def div(*text, **attributes)
435
- return self if text.empty?
436
- tab_att, att = Utils.split_table_attr(attributes)
437
- tab_att[:border_around] = true
438
- table(**tab_att) { |table| table.add { _1.add(*text, **att) } }
439
- end
440
-
441
- # Dump given values as vertical bars.
442
- #
443
- # @example Draw green bars
444
- # ui.vbars 1..10, style: :green
445
- #
446
- # @example Draw very big bars
447
- # ui.vbars 1..10, bar_width: 5, height: 20
448
- #
449
- # @param values [#to_a, Array<Numeric>] values to print
450
- # @param normalize [true, false] whether the values should be normalized
451
- # @param height [Integer] output height
452
- # @param bar_width [:auto, :min, Integer] with of each bar
453
- # @param style [Symbol, Array<Symbol>, nil] drawing style
454
- #
455
- # @raise [ArgumentError] if any value is negative
456
- #
457
- # @return (see puts)
291
+ # Prints a vertical bar chart.
292
+ #
293
+ # All values must be non-negative.
294
+ #
295
+ # @example Simple bar chart
296
+ # ui.vbars [3, 7, 2, 9, 5]
297
+ #
298
+ # @example Normalised chart with custom height
299
+ # ui.vbars [10, 40, 25, 60], normalize: true, height: 15
300
+ #
301
+ # @param values [#each]
302
+ # collection of non-negative `Numeric` values
303
+ # @param min [Numeric, nil]
304
+ # lower bound for scaling; `nil` uses the data minimum
305
+ # @param max [Numeric, nil]
306
+ # upper bound for scaling; `nil` uses the data maximum
307
+ # @param normalize [Boolean]
308
+ # when `true` applies min-max normalization;
309
+ # when `false` scales each bar relative to the maximum value
310
+ # @param height [#to_int]
311
+ # chart height in lines (minimum 3; default: 10)
312
+ # @param bar_width [#to_int, :auto]
313
+ # width of each bar in characters; `:auto` fits the graph in available
314
+ # terminal width
315
+ # @param style [Symbol, String, Array<Style>, Array<String>, nil]
316
+ # ANSI style applied to the bars
317
+ # @return (see #puts)
458
318
  def vbars(
459
319
  values,
320
+ min: nil,
321
+ max: nil,
460
322
  normalize: false,
461
323
  height: 10,
462
- bar_width: :auto,
324
+ bar_width: 3,
463
325
  style: nil
464
326
  )
465
- return self if (values = values.to_a).empty?
466
- if values.any?(&:negative?)
467
- raise(ArgumentError, 'values can not be negative')
468
- end
469
- puts(
470
- *VBarsRenderer.lines(
471
- values,
472
- columns,
473
- height,
474
- normalize,
475
- bar_width,
476
- Terminal.ansi? ? style : nil
477
- )
478
- )
327
+ bars = VBars.new(values, min, max, normalize)
328
+ return self if bars.empty?
329
+ raise(ArgumentError, 'values can not be negative') unless bars.valid?
330
+ puts(*bars.lines(columns, height, bar_width, style), bbcode: false)
479
331
  end
480
332
 
481
- # Dump given values as horizontal bars.
333
+ # Prints a horizontal bar chart.
482
334
  #
483
- # @example Draw green bars
484
- # ui.hbars 1..10, style: :green
335
+ # All values must be non-negative.
485
336
  #
486
- # @example Draw bars in half sreen width
487
- # ui.hbars 1..10, style: :blue, width: 0.5
337
+ # @example Simple horizontal chart
338
+ # ui.hbars [3, 7, 2, 9, 5]
488
339
  #
489
- # @param values [#to_a, Array<Numeric>] values to print
490
- # @param min [#to_f] start value
491
- # @param max [#to_f] end value
492
- # @param normalize [true, false] whether the values should be normalized
493
- # @param text [true, false] whether the values should be printed too
494
- # @param width [:auto, :min, Integer] with of each bar
495
- # @param style [Symbol, Array<Symbol>, nil] bar drawing style
496
- # @param text_style [Symbol, Array<Symbol>, nil] text style
340
+ # @example Chart without value labels
341
+ # ui.hbars [10, 40, 25, 60], text: false, normalize: true
497
342
  #
498
- # @raise [ArgumentError] if any value is negative
499
- #
500
- # @return (see puts)
343
+ # @param values (see #vbars)
344
+ # @param min (see #vbars)
345
+ # @param max (see #vbars)
346
+ # @param normalize (see #vbars)
347
+ # @param style (see #vbars)
348
+ # @param width [#to_int, Float, :auto] maximum bar width in characters;
349
+ # a `Float` is a fraction of available width; `:auto` fills available width
350
+ # @param text [Boolean] when `true` prints the numeric value next to each bar
351
+ # @param text_style [Symbol, String, Array<Style>, Array<String>, nil]
352
+ # ANSI style applied to the value labels
353
+ # @return (see #puts)
501
354
  def hbars(
502
355
  values,
503
356
  min: nil,
504
357
  max: nil,
505
358
  normalize: false,
506
- text: true,
507
359
  width: :auto,
508
360
  style: nil,
361
+ text: true,
509
362
  text_style: nil
510
363
  )
511
- return self if (values = values.to_a).empty?
512
- if values.any?(&:negative?)
513
- raise(ArgumentError, 'values can not be negative')
514
- end
515
- style = text_style = nil unless Terminal.ansi?
516
- renderer = HBarsRenderer.new(values, min, max)
517
- renderer.with_text(text_style) if text
518
- puts(*renderer.lines(Utils.as_size(3..columns, width), style, normalize))
364
+ bars = HBars.new(values, min, max, normalize)
365
+ return self if bars.empty?
366
+ raise(ArgumentError, 'values can not be negative') unless bars.valid?
367
+ bars.with_text(text_style) if text
368
+ puts(*bars.lines(columns, width, style), bbcode: false)
519
369
  end
520
370
 
521
- # Dynamically display a task progress.
522
- # When a `max` parameter is given the progress will be displayed as a
523
- # progress bar below the `title`. Otherwise the progress is displayed just
524
- # by accumulating dots.
525
- #
526
- # @example Display a progress bar
527
- # ui.progress('Download file', max: 1024) do |progress|
528
- # while progress.value < progress.max
529
- # # just to simulate the download
530
- # sleep(0.1)
531
- # bytes_read = rand(10..128)
532
- #
533
- # # here we actualize the progress
534
- # progress.value += bytes_read
535
- # end
536
- # end
371
+ # Renders a {Table} to the terminal.
372
+ #
373
+ # The method yields a {Table} object for population; if no block is given
374
+ # nothing is rendered.
537
375
  #
538
- # @example Display simple progress
539
- # progress = ui.progress 'Check some stuff'
540
- # 10.times do
541
- # # simulate some work
542
- # sleep 0.1
376
+ # Border name symbols:
377
+ # `:rounded` (default), `:single`, `:double`, `:heavy`,
378
+ # `:single_double`, `:double_single`, `:single_heavy`, `:heavy_single`
543
379
  #
544
- # # here we actualize the progress
545
- # progress.step
380
+ # @example Simple table with a double outer frame
381
+ # ui.table border_frame: :double do |t|
382
+ # t.add_row '[b]Name', '[b]Score', align: :center
383
+ # t.add_row 'Alice', 42
384
+ # t.add_row 'Bob', 17
546
385
  # end
547
- # progress.ok 'Stuff checked ok'
548
- #
549
- # @overload progress(title, max: nil, pin: false)
550
- # @param title [#to_s]
551
- # title text to display
552
- # @param max [#to_f]
553
- # expected maximum value
554
- # @param pin [true, false]
555
- # whether the final progress state should be "pinned" to parent element
556
- #
557
- # @return [ProgressHelper]
558
- # itself
559
- #
560
- # @overload progress(title, max: nil, pin: false, &block)
561
- # @param title [#to_s]
562
- # title text
563
- # @param max [#to_f]
564
- # expected maximum value
565
- # @param pin [true, false]
566
- # whether the final progress state should be "pinned" to parent element
567
- #
568
- # @yieldparam progress [ProgressHelper]
569
- # itself
570
- #
571
- # @return [Object]
572
- # the result of the given block
573
- def progress(title, max: nil, pin: false, &block)
574
- __with(
575
- if Terminal.ansi?
576
- Progress.new(self, title, max, pin)
577
- else
578
- DumbProgress.new(self, title, max)
579
- end,
580
- &block
386
+ #
387
+ # @option options [:default, :none, :single, :double, :heavy] :border (:default)
388
+ # default border used for all border parts
389
+ # @option options [nil, :none, :single, :double, :heavy] :border_frame
390
+ # outer frame; falls back to `:border`
391
+ # @option options [nil, :none, :single, :double, :heavy] :border_vertical
392
+ # vertical column separators; falls back to `:border`
393
+ # @option options [nil, :none, :single, :double, :heavy] :border_horizontal
394
+ # horizontal row separators; falls back to `:border`
395
+ # @option options [Symbol, String, Array<Style>, Array<String>, nil] :border_style
396
+ # ANSI style applied to all borders
397
+ # @option options [Symbol, Array<Symbol>] :frame (:all)
398
+ # which frame sides to draw: `:all` or an enumerable of
399
+ # `:top`, `:right`, `:bottom`, `:left`
400
+ # @option options [#to_int, Float, :max, nil] :width
401
+ # `:max` expands to the full terminal width;
402
+ # a negative `#to_int` is an offset from the terminal width;
403
+ # a `Float` is a fraction of the terminal width
404
+ # @yield [table] a {Table} instance to populate with rows and cells
405
+ # @yieldparam table [Table] the table
406
+ # @return [Features]
407
+ def table(**options)
408
+ return self unless block_given?
409
+ yield(table = Table.new)
410
+ return self if table.empty?
411
+ puts(
412
+ *TableRenderer.lines(columns, table, **options),
413
+ bbcode: false,
414
+ eol: :no_paragraph
581
415
  )
582
416
  end
583
417
 
584
- # Execute a program.
585
418
  #
586
- # @example Execute a simple command
587
- # ui.sh 'ls'
419
+ # @!endgroup
588
420
  #
589
- # @example Execute a command wih arguments
590
- # ret = ui.sh('curl', '--version')
591
- # raise('Curl not found') unless ret&.success?
421
+
592
422
  #
593
- # @example Execute shell commands
594
- # ui.sh "mkdir 'test' && cd 'test'"
423
+ # @!group Section elements
595
424
  #
596
- # @example Execute a command with environment variables
597
- # ui.sh({'ENV' => 'test'}, 'rails')
425
+
426
+ # Creates a {Temporary} element whose output is erased when it closes.
598
427
  #
599
- # @see run
428
+ # @example Manual close
429
+ # tmp = ui.temporary
430
+ # tmp.puts 'Loading…'
431
+ # sleep 1
432
+ # tmp.end # erases "Loading…"
600
433
  #
601
- # @param cmd (see run)
602
- # @param preserve_spaces (see run)
603
- # @param options (see run)
604
- # @return [Process::Status]
605
- # when command was executed
606
- # @return [nil]
607
- # in error case (like command not found)
608
- def sh(*cmd, preserve_spaces: false, **options)
609
- ShellRenderer.sh(self, cmd, options, preserve_spaces)
610
- end
611
-
612
- # Execute a shell program and return output. Limit the lines displayed.
613
- #
614
- # @example Capture output and error
615
- # status, out, err = ui.run('ls ./ && ls ./this_does_not_exist')
616
- # # => #<Process::Status: pid 25562 exit 1>
617
- # # => [...] # the output of first `ls`
618
- # # => ["ls: ./this_does_not_exist: No such file or directory"]
619
- #
620
- # @see sh
621
- #
622
- # @param cmd [String]
623
- # command and optional arguments
624
- # @param preserve_spaces [true,false]
625
- # whether the spaces and tabs of the output should be preserve
626
- # @param max_lines [Integer]
627
- # limit of displayed lines
628
- # @param options [Hash] executions options
629
- # @return [[Process::Status, Array<String>, Array<String>]]
630
- # process status, output and error output when command was executed
631
- # @return [nil]
632
- # in error case (like command not found)
633
- def run(*cmd, preserve_spaces: false, max_lines: 10, **options)
634
- result =
635
- ShellRenderer.run(
636
- self,
637
- cmd,
638
- options,
639
- preserve_spaces,
640
- max_lines.clamp(1, Terminal.rows)
641
- )
642
- result if result[0]
643
- end
434
+ # @example Block form
435
+ # ui.temporary do
436
+ # ui.puts 'Thinking…'
437
+ # sleep 2
438
+ # end # "Thinking…" is erased here
439
+ #
440
+ # @yield [temp] the {Temporary} element
441
+ # @yieldparam temp [Temporary]
442
+ # @return [Object] return value of the block
443
+ # @return [Temporary] itself, if no block is specified
444
+ def temporary(&) = __with(Temporary.new(self), &)
644
445
 
446
+ # Creates a {Margin} element that adds horizontal and vertical whitespace.
645
447
  #
646
- # @!endgroup
448
+ # @example
449
+ # ui.margin 0, 0.25 do
450
+ # ui.puts 'This text has 25% width horizontal margin.'
451
+ # end
647
452
  #
648
-
453
+ # @overload margin(value)
454
+ # Margin for all sides.
455
+ # @param value [#to_int, Float] margin of all four sides
456
+ #
457
+ # @overload margin(vertical = 0, horizontal = 1)
458
+ # Seperate vertical and horizontal margin.
459
+ # @param vertical [#to_int] top and bottom margin
460
+ # @param horizontal [#to_int, Float] left and right margin
461
+ #
462
+ # @overload margin(top, horizontal, bottom)
463
+ # Seperate top, bottom and horizontal margin.
464
+ # @param top [#to_int] top margin
465
+ # @param horizontal [#to_int, Float] left and right margin
466
+ # @param bottom [#to_int] bottom margin
467
+ #
468
+ # @overload margin(top, right, bottom, left)
469
+ # Seperate margins.
470
+ # @param top [#to_int] top margin
471
+ # @param right [#to_int, Float] right margin
472
+ # @param bottom [#to_int] bottom margin
473
+ # @param left [#to_int, Float] left margin
474
+ #
475
+ # @overload margin(top: 0, right: 0, bottom: 0, left: 0)
476
+ # Specific margin.
477
+ # @param top [#to_int] top margin
478
+ # @param right [#to_int, Float] right margin
479
+ # @param bottom [#to_int] bottom margin
480
+ # @param left [#to_int, Float] left margin
481
+ #
482
+ # @yield [margin] the {Margin} element
483
+ # @yieldparam margin [Margin]
484
+ # @return [Object] return value of the block
485
+ # @return [Margin] itself, if no block is specified
486
+ def margin(*, &) = __with(Margin.new(self, *), &)
487
+
488
+ # Creates a {Section} element — a bordered container with an optional title.
489
+ #
490
+ # @example Manual close
491
+ # sec = ui.section 'Results'
492
+ # sec.ok 'All good'
493
+ # sec.end
494
+ #
495
+ # @example Block form with title
496
+ # ui.section 'Summary' do
497
+ # ui.puts '3 files processed.'
498
+ # end
649
499
  #
650
- # @!group Sub-Elements
500
+ # @example Block form without title
501
+ # ui.section do
502
+ # ui.puts 'Anonymous section content.'
503
+ # end
651
504
  #
652
-
653
- # Create a visually separated section for the output of text elements.
654
- # Besides this simple sections there exist sections of different kinds:
655
- # {#message}, {#information}, {#warning}, {#error}, {#failed}, {#framed}
656
- #
657
- # Like any other {Element} sections support all {Features}.
658
- #
659
- # @overload section#
660
- # @example
661
- # ui.section do |section|
662
- # section.h1 'About Sections'
663
- # section.space
664
- # section.puts 'Sections are areas of text elements.'
665
- # section.puts 'You can use any other feature inside such an area.'
666
- # end
667
- # # => ╭────╶╶╶
668
- # # => │ ╴╶╴╶─═══ About Sections ═══─╴╶╴╶
669
- # # => │
670
- # # => │ Sections are areas of text elements.
671
- # # => │ You can use any other feature inside such an area.
672
- # # => ╰──── ─╶╶╶
673
- #
674
- # @yieldparam section [Section]
675
- # itself
676
- # @return [Object]
677
- # the result of the given block
678
- #
679
- # @overload section(*text, **options)
680
- #
681
- # @example
682
- # section = ui.section
683
- # section.h1 'About Sections'
684
- # section.space
685
- # section.puts 'Sections are areas of text elements.'
686
- # section.end # close the section
687
- #
688
- # @param text [*#to_s]
689
- # optional objects to print line by line
690
- # @param options [{Symbol => Object}]
691
- # print options – see {puts}
692
- # @return [Section]
693
- # itself
694
- def section(*text, **options, &block)
695
- __sec(:default, false, nil, text, options, &block)
505
+ # @param title [#to_s, nil]
506
+ # optional header text
507
+ # @param type [:default, :message, :information, :warning, :error, :fatal]
508
+ # visual style of the section
509
+ # @param border [:default, :rounded, :single, :double, :heavy]
510
+ # border style
511
+ # @yield [section] the {Section} element
512
+ # @yieldparam section [Section]
513
+ # @return [Object] return value of the block
514
+ # @return [Section] itself, if no block is specified
515
+ def section(title = nil, type: :default, border: :default, &)
516
+ __with(Section.new(self, title, type, border), &)
696
517
  end
697
518
  alias begin section
698
519
 
699
- # @!macro msg_like
700
- #
701
- # Like any other {Element} sections support all {Features}.
702
- #
703
- # @overload $0(title)
704
- # @param title [#to_s]
705
- # section title
706
- # @yieldparam message [Section]
707
- # itself
708
- # @return [Object]
709
- # the result of the given block
710
- # @overload $0(title, *text, **options)
711
- # @param title [#to_s]
712
- # section title
713
- # @param text [*#to_s]
714
- # optional objects to print line by line
715
- # @param options [{Symbol => Object}]
716
- # print options – see {puts}
717
- # @return [Section]
718
- # itself
719
-
720
- # Create a visually separated section with title for the output of text
721
- # elements.
722
- # @macro msg_like
723
- def message(title, *text, **options, &block)
724
- __sec(:message, false, title, text, options, &block)
520
+ # Creates a {Section} with `:message` styling.
521
+ #
522
+ # @param title [#to_s] header text
523
+ # @param border (see #section)
524
+ # @yield (see #section)
525
+ # @yieldparam (see #section)
526
+ # @return (see #section)
527
+ def message(title, border: :default, &)
528
+ section(title, border:, type: :message, &)
725
529
  end
726
530
  alias msg message
727
531
 
728
- # Create a visually separated section marked as informational with title for
729
- # the output of text elements.
532
+ # Creates a {Section} with `:information` styling.
730
533
  #
731
- # @macro msg_like
732
- def information(title, *text, **options, &block)
733
- __sec(:information, true, title, text, options, &block)
534
+ # @param (see #message)
535
+ # @yield (see #message)
536
+ # @yieldparam (see #message)
537
+ # @return (see #message)
538
+ def information(title, border: :default, &)
539
+ section(title, border:, type: :information, &)
734
540
  end
735
541
  alias info information
736
542
 
737
- # Create a visually separated section marked as warning with title for
738
- # the output of text elements.
543
+ # Creates a {Section} with `:warning` styling.
739
544
  #
740
- # @macro msg_like
741
- def warning(title, *text, **options, &block)
742
- __sec(:warning, true, title, text, options, &block)
545
+ # @param (see #message)
546
+ # @yield (see #message)
547
+ # @yieldparam (see #message)
548
+ # @return (see #message)
549
+ def warning(title, border: :default, &)
550
+ section(title, border:, type: :warning, &)
743
551
  end
744
552
  alias warn warning
745
553
 
746
- # Create a visually separated section marked as error with title for
747
- # the output of text elements.
554
+ # Creates a {Section} with `:error` styling.
748
555
  #
749
- # @macro msg_like
750
- def error(title, *text, **options, &block)
751
- __sec(:error, true, title, text, options, &block)
556
+ # @param (see #message)
557
+ # @yield (see #message)
558
+ # @yieldparam (see #message)
559
+ # @return (see #message)
560
+ def error(title, border: :default, &)
561
+ section(title, border:, type: :error, &)
752
562
  end
753
- alias err error
754
563
 
755
- # Create a visually separated section marked as failure with title for
756
- # the output of text elements.
564
+ # Creates a {Section} with `:fatal` styling.
757
565
  #
758
- # @macro msg_like
759
- def failed(title, *text, **options, &block)
760
- __sec(:failed, true, title, text, options, &block)
761
- end
762
-
763
- # Create a framed section.
764
- # Like any other {Element} sections support all {Features}.
765
- #
766
- # @overload framed(align: :left, border: :default, border_style: nil)
767
- # @param align [:left, :right, :centered]
768
- # text alignment,
769
- # see {Attributes::Align}
770
- # @param border [Symbol]
771
- # kind of border,
772
- # see {Attributes::Border}
773
- # @param border_style [Enumerable<Symbol>]
774
- # style of border,
775
- # see {Attributes::BorderStyle}
776
- # @yieldparam framed [Element]
777
- # itself
778
- # @return [Object] the result of the given block
779
- # @overload framed(*text, align: :left, border: :default, border_style: nil, **options)
780
- # @param text [*#to_s]
781
- # optional objects to print line by line
782
- # @param align [:left, :right, :centered]
783
- # text alignment,
784
- # see {Attributes::Align}
785
- # @param border [Symbol]
786
- # kind of border,
787
- # see {Attributes::Border}
788
- # @param border_style [Enumerable<Symbol>]
789
- # style of border,
790
- # see {Attributes::BorderStyle}
791
- # @param options [{Symbol => Object}]
792
- # print options – see {puts}
793
- # @return [Element] itself
794
- def framed(
795
- *text,
796
- align: :left,
797
- border: :default,
798
- border_style: nil,
799
- **options,
800
- &block
801
- )
802
- __with(
803
- Framed.new(
804
- self,
805
- Utils.align(align),
806
- Theme.current.border(border),
807
- Utils.style(border_style)
808
- ),
809
- *text,
810
- **options,
811
- &block
812
- )
566
+ # @param (see #message)
567
+ # @yield (see #message)
568
+ # @yieldparam (see #message)
569
+ # @return (see #message)
570
+ def fatal(title, border: :default, &)
571
+ section(title, border:, type: :fatal, &)
813
572
  end
814
573
 
815
- # Generate a task section.
574
+ # Creates a {Frame} element — a bordered box with an optional title.
816
575
  #
817
- # @param title [#to_s]
818
- # task title text
819
- # @param text [*#to_s]
820
- # optional objects to print line by line
821
- # @param pin [true, false] whether to keep text "pinned"
822
- # @param options [{Symbol => Object}]
823
- # print options – see {puts}
576
+ # @example Manual close
577
+ # frm = ui.frame 'Preview'
578
+ # frm.puts 'Content inside the frame.'
579
+ # frm.end
824
580
  #
825
- # @yieldparam task [Task] itself
581
+ # @example Block form with title and custom border
582
+ # ui.frame 'Results', border: :double do
583
+ # ui.puts 'All checks passed.'
584
+ # end
585
+ #
586
+ # @example Block form without a title
587
+ # ui.frame do
588
+ # ui.puts 'Framed content.'
589
+ # end
826
590
  #
827
- # @return [Task] itself
828
- def task(title, *text, pin: false, **options, &block)
829
- __with(Task.new(self, title, pin), *text, **options, &block)
591
+ # @param title (see #section)
592
+ # @param border (see #section)
593
+ # @param style [Symbol, String, Array<Style>, Array<String>, nil]
594
+ # ANSI style applied to the frame
595
+ # @yield [frame] the {Frame} element
596
+ # @yieldparam frame [Frame]
597
+ # @return [Object] return value of the block
598
+ # @return [Frame] itself, if no block is specified
599
+ def frame(title = nil, border: :default, style: nil, &)
600
+ __with(Frame.new(self, title, border, style), &)
830
601
  end
831
602
 
603
+ # Creates a {Task} element — a labelled step that shows a spinner while
604
+ # running and a check mark on success.
832
605
  #
833
- # @!endgroup
834
- #
835
-
606
+ # @example Manual close
607
+ # t = ui.task 'Installing dependencies'
608
+ # run_install
609
+ # t.end
836
610
  #
837
- # @!group User Interaction
611
+ # @example Block form
612
+ # ui.task 'Installing dependencies' do
613
+ # run_install
614
+ # end
838
615
  #
616
+ # @param title [#to_s] task description
617
+ # @param pin [Boolean] whether the task title should be pinned
618
+ # @yield [task] the {Task} element
619
+ # @yieldparam task [Task]
620
+ # @return [Object] return value of the block
621
+ # @return [Task] itself, if no block is specified
622
+ def task(title, pin: false, &) = __with(Task.new(self, title, pin), &)
839
623
 
840
- # Wait for user input.
624
+ # Creates a {Progress} element for tracking incremental work.
841
625
  #
842
- # @example Wait until user wants to coninue
843
- # ui.await { ui.puts '[faint][\\Press ENTER to continue...][/faint]' }
626
+ # When `max` is given the progress is displayed as a percentage bar.
627
+ # When `max` is `nil` an open-ended dot animation is shown instead.
844
628
  #
845
- # @example Ask yes/no-question
846
- # ui.await(yes: %w[j o t s y d Enter], no: %w[n Esc]) do
847
- # ui.puts 'Do you like NayttUI?'
629
+ # @example Bounded progress
630
+ # ui.progress 'Processing', max: items.size do |bar|
631
+ # items.each { process(it); bar.step }
848
632
  # end
849
- # # => true, for user's YES
850
- # # => false, for user's NO
851
- # # Info:
852
- # # The keys will work for Afrikaans, Dutch, English, French, German,
853
- # # Italian, Polish, Portuguese, Romanian, Spanish and Swedish.
854
- #
855
- # @overload await(yes: 'Enter', no: 'Esc')
856
633
  #
857
- # @overload await(yes: 'Enter', no: 'Esc', &block)
858
- # @yieldparam temp [Temporary]
859
- # temporary displayed section (section will be erased after input)
860
- #
861
- # @param yes [String, Enumerable<String>]
862
- # key code/s a user can input to return positive result
863
- # @param no [String, Enumerable<String>]
864
- # key code/s a user can input to return negative resault
634
+ # @example Open-ended progress
635
+ # ui.progress 'Working…' do |bar|
636
+ # loop { bar.step; break if done? }
637
+ # end
865
638
  #
866
- # @return [true, false]
867
- # whether the user inputs a positive result
868
- # @return [nil]
869
- # in error case
870
- def await(yes: 'Enter', no: 'Esc')
871
- return __await(yes, no) unless block_given?
872
- temporary do |temp|
873
- yield(temp)
874
- __await(yes, no)
875
- end
639
+ # @param title (see #section)
640
+ # @param max [Numeric, nil] maximum value
641
+ # @param popts (see #mark)
642
+ # @yield [progress] the {Progress} element
643
+ # @yieldparam progress [Progress]
644
+ # @return [Object] return value of the block
645
+ # @return [Progress] itself when no block is given
646
+ def progress(*title, max: nil, **popts, &)
647
+ __with(
648
+ (Terminal.ansi? ? Progress : DumbProgress).new(
649
+ self,
650
+ max,
651
+ *title,
652
+ **popts
653
+ ),
654
+ &
655
+ )
876
656
  end
877
657
 
878
- # Allows the user to select an option from a selection.
879
- # The selected option is returned.
880
- #
881
- # @overload choice(*choices, abortable: false)
882
- # @param choices [#to_s]
883
- # one or more alternatives to select from
884
- # @param abortable [true, false]
885
- # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
886
- #
887
- # @return [Integer]
888
- # index of selected choice
889
- # @return [nil]
890
- # when user aborted the selection
891
- #
892
- # @overload choice(*choices, abortable: false, &block)
893
- # @example Request a fruit
894
- # ui.choice('Apple', 'Banana', 'Orange') { ui.puts 'What do you prefer?' }
895
- # # => 0, when user likes apples
896
- # # => 1, when bananas are user's favorite
897
- # # => 2, when user is a oranges lover
898
- #
899
- # @param choices[#to_s]
900
- # one or more alternatives to select from
901
- # @param abortable[true, false]
902
- # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
903
- #
904
- # @yieldparam temp [Temporary]
905
- # temporary displayed section (section will be erased after input)
906
- #
907
- # @return [Integer]
908
- # index of selected choice
909
- # @return [nil]
910
- # when user aborted the selection
911
- #
912
- # @overload choice(**choices, abortable: false)
913
- # @param choices [#to_s]
914
- # one or more alternatives to select from
915
- # @param abortable [true, false]
916
- # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
917
- # @param selected [#to_s, nil]
918
- # optionally pre-selected option
919
- #
920
- # @return [Object]
921
- # key for selected choice
922
- # @return [nil]
923
- # when user aborted the selection
924
- #
925
- # @overload choice(**choices, abortable: false, &block)
926
- # @example Request a preference
927
- # ui.choice(
928
- # k: 'Kitty',
929
- # i: 'iTerm2',
930
- # g: 'Ghostty',
931
- # t: 'Tabby',
932
- # r: 'Rio',
933
- # abortable: true
934
- # ) { ui.puts 'Which terminal emulator do you like?' }
935
- # # => whether the user selected: :k, :i, :g, :t, :r
936
- # # => nil, when the user aborted
937
- # @param choices[#to_s]
938
- # one or more alternatives to select from
939
- # @param abortable[true, false]
940
- # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
941
- # @param selected[Integer]
942
- # pre-selected option index
943
- #
944
- # @yieldparam temp [Temporary]
945
- # temporary displayed section (section will be erased after input)
946
- #
947
- # @return [Object]
948
- # key for selected choice
949
- # @return [nil]
950
- # when user aborted the selection
951
- #
952
- def choice(*choices, abortable: false, selected: nil, **kwchoices, &block)
953
- return if choices.empty? && kwchoices.empty?
954
- choice =
955
- if Terminal.ansi?
956
- Choice.new(self, choices, kwchoices, abortable, selected)
957
- else
958
- DumbChoice.new(self, choices, kwchoices, abortable)
959
- end
960
- __with(choice) { choice.select(&block) }
961
- end
658
+ #
659
+ # @!endgroup
660
+ #
661
+ # @!group User Interaction
662
+ #
962
663
 
963
- # Allows the user to select from several options.
964
- # All options are returned with their selection status.
664
+ # Waits for the user to press any key.
965
665
  #
966
- # @param choices [{#to_s => [true,false]}]
967
- # Hash of options and their selection state
968
- # @param abortable [true, false]
969
- # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
970
- # @param selected [#to_s, nil]
971
- # optionally pre-selected key
666
+ # @example Plain wait
667
+ # ui.puts 'Press any key to continue…'
668
+ # ui.await
972
669
  #
973
- # @yieldparam temp [Temporary]
974
- # temporary displayed section (section will be erased after input)
670
+ # @example With temporary prompt
671
+ # ui.await { ui.puts '[faint]Press any key to continue…' }
975
672
  #
976
- # @return [{#to_s => [true,false]}]
977
- # Hash of options and their selection state
673
+ # @yield (see #temporary)
674
+ # @yieldparam (see #temporary)
978
675
  # @return [nil]
979
- # when user aborted the selection
980
- def options(abortable: false, selected: nil, **choices, &block)
981
- return {} if choices.empty?
982
- options =
983
- if Terminal.ansi?
984
- Options.new(self, choices, abortable, selected)
985
- else
986
- DumbOptions.new(self, choices, abortable, selected)
987
- end
988
- __with(options) { options.select(&block) }
676
+ def await
677
+ yield(temp = temporary) if block_given?
678
+ Terminal.read_key_event
679
+ nil
680
+ ensure
681
+ temp&.end
989
682
  end
990
683
 
991
- # Allows the user to select from several options.
992
- # The selected options are returned.
684
+ # Waits for a key event and returns information about it.
993
685
  #
994
- # @example Select a terminal
995
- # ui.select %w[Kitty iTerm2 Ghostty Tabby Rio] do
996
- # ui.puts '[i]Which terminal applications did you already tested?[/i]'
997
- # end
686
+ # Key names are strings such as `"a"`, `"Enter"`, `"Esc"`, `"Back"`,
687
+ # `"Shift+Alt+F1"`.
998
688
  #
999
- # @param choices [Array<#to_s>]
1000
- # selectable options
1001
- # @param abortable [true, false]
1002
- # whether the user is allowed to abort with 'Esc' or 'Ctrl+c'
1003
- # @param selected [Integer, :all, nil]
1004
- # optionally pre-selected option index or `:all` to pre-select all items
1005
- # @yieldparam temp [Temporary]
1006
- # temporary displayed section (section will be erased after input)
689
+ # @example
690
+ # answer = ui.query yes: 'y', no: 'n'
691
+ # ui.puts answer == :yes ? 'Confirmed!' : 'Cancelled.'
1007
692
  #
1008
- # @return [Array<#to_s>]
1009
- # selected options
1010
- # @return [nil]
1011
- # when user aborted the selection
1012
- def select(*choices, abortable: false, selected: nil, &block)
1013
- return [] if choices.empty?
1014
- choices = choices[0] if choices.size == 1 && choices[0].is_a?(Enumerable)
1015
- if selected == :all
1016
- sel = true
1017
- elsif selected
1018
- selected = choices[selected.to_i]
693
+ # @example With a temporary prompt
694
+ # answer = ui.query(yes: 'y', no: 'n') do
695
+ # ui.puts 'Continue? ([b]y[/b]/[b]n[/b])'
696
+ # end
697
+ #
698
+ # @param options [Hash<Object => String, #each>]
699
+ # map of return values to key names or enumerables of key names;
700
+ # e.g. `{ yes: 'y', no: %w[n Esc] }`
701
+ # @yield (see #temporary)
702
+ # @yieldparam (see #temporary)
703
+ # @return [Object] matched option key
704
+ def query(**options)
705
+ yield(temp = temporary) if block_given?
706
+ return Terminal.read_key_event.name if options.empty?
707
+ Terminal.on_key_event do |event|
708
+ event = event.name
709
+ found, =
710
+ options.find do |_, value|
711
+ (value.is_a?(Enumerable) && value.include?(event)) || value == event
712
+ end
713
+ break found if found
1019
714
  end
1020
- options(
1021
- abortable: abortable,
1022
- selected: selected,
1023
- **choices.to_h { [_1, sel] },
1024
- &block
1025
- ).filter_map { |key, selected| key if selected }
715
+ ensure
716
+ temp&.end
1026
717
  end
1027
718
 
719
+ # Presents a list of options and returns the one the user selects.
1028
720
  #
1029
- # @!endgroup
721
+ # In ANSI mode the user navigates with arrow keys and confirms with Enter.
722
+ # In dumb mode items are numbered and the user types the item number.
1030
723
  #
1031
-
724
+ # @example Positional items
725
+ # answer = ui.choice 'Yes', 'No', 'Cancel'
1032
726
  #
1033
- # @!group Utilities
727
+ # @example Positional items
728
+ # answer = ui.choice 'Yes', 'No', abortable: true do
729
+ # ui.puts 'Overwrite the file?'
730
+ # end
1034
731
  #
732
+ # @example Keyword pairs
733
+ # action = ui.choice(overwrite: 'Overwrite', skip: 'Skip') do
734
+ # ui.puts 'File already exists.'
735
+ # end
736
+ #
737
+ # @overload choice(*items, abortable: false, selected: nil)
738
+ # Items are passed as positional arguments; the selected item itself is
739
+ # returned.
740
+ # @param items [#to_s, ...] options to choose from
741
+ # @param abortable [Boolean] when `true` the user can press Esc to cancel
742
+ # @param selected pre-selected item value, or `nil`
743
+ #
744
+ # @overload choice(abortable: false, selected: nil, **pairs)
745
+ # Items are passed as keyword pairs `{ return_value => label }`; the
746
+ # matching key is returned.
747
+ # @param abortable [Boolean] when `true` the user can press Esc to cancel
748
+ # @param selected pre-selected return value, or `nil`
749
+ # @param pairs [Hash{Object => #to_s}] map of return values to display labels
750
+ #
751
+ # @yield (see #temporary)
752
+ # @yieldparam (see #temporary)
753
+ # @return [Object] the key of the selected pair, or `nil` if aborted
754
+ def choice(*items, abortable: false, selected: nil, **pairs)
755
+ return if items.empty? && pairs.empty?
756
+ yield(temp = temporary) if block_given?
757
+ (Terminal.ansi? ? Choice : DumbChoice).new(
758
+ self,
759
+ items + pairs.values,
760
+ Array.new(items.size, &:itself) + pairs.keys
761
+ ).select(abortable, selected)
762
+ ensure
763
+ temp&.end
764
+ end
1035
765
 
1036
- # @private
1037
- def columns = Terminal.columns
1038
-
1039
- # Display some temporary content.
1040
- # The content displayed in the block will be erased after the block ends.
766
+ # Presents a list of options and returns all items the user selects.
1041
767
  #
1042
- # @example
1043
- # ui.temporary(<<~MSG) { ui.await }
1044
- # This is a [i]temporary[/i] displayed text.
1045
- # It will disappear when you press [b]ENTER[/b].
1046
- # MSG
768
+ # In ANSI mode the user toggles items with Space and confirms with Enter.
769
+ # In dumb mode items are numbered and the user types item numbers.
1047
770
  #
1048
- # @example
1049
- # ui.temporary do
1050
- # ui.information 'Hint' do
1051
- # ui.puts 'This is a [i]temporary[/i] displayed text.'
1052
- # ui.puts 'It will disappear when you press [b]ENTER[/b].'
1053
- # end
1054
- # ui.await
771
+ # @example Positional items
772
+ # picks = ui.select 'Kitty', 'iTerm2', 'Ghostty'
773
+ #
774
+ # @example Positional items with all pre-selected
775
+ # picks = ui.select 'A', 'B', 'C', selected: :all do
776
+ # ui.puts 'Choose features to enable:'
1055
777
  # end
1056
778
  #
1057
- # @yieldparam temp [Temporary]
1058
- # itself
779
+ # @example Keyword pairs
780
+ # flags = ui.select verbose: 'Verbose', debug: 'Debug', trace: 'Trace'
781
+ #
782
+ # @overload select(*items, abortable: false, selected: nil)
783
+ # Items are passed as positional arguments; the selected items themselves
784
+ # are returned.
785
+ # @param items [#to_s, ...] options to choose from
786
+ # @param abortable [Boolean] when `true` the user can press Esc to cancel
787
+ # @param selected [nil, :all, #each]
788
+ # pre-selected items: `nil` = none, `:all` = all,
789
+ # or an enumerable of item values to pre-select
790
+ #
791
+ # @overload select(abortable: false, selected: nil, **pairs)
792
+ # Items are passed as keyword pairs `{ return_value => label }`; the
793
+ # matching keys are returned.
794
+ # @param abortable [Boolean] when `true` the user can press Esc to cancel
795
+ # @param selected [nil, :all, #each]
796
+ # @param pairs [Hash{Object => #to_s}] map of return values to labels
797
+ #
798
+ # @yield (see #temporary)
799
+ # @yieldparam (see #temporary)
800
+ # @return [Array] keys of selected pairs, or `nil` if aborted
801
+ def select(*items, abortable: false, selected: nil, **pairs)
802
+ return if items.empty? && pairs.empty?
803
+ yield(temp = temporary) if block_given?
804
+ items = items.to_h { [it, it] }.merge!(pairs)
805
+ (Terminal.ansi? ? Select : DumbSelect).new(
806
+ self,
807
+ if selected == :all
808
+ items.map { it << true }
809
+ elsif selected.is_a?(Enumerable)
810
+ items.map { |ret, txt| [ret, txt, selected.include?(ret)] }
811
+ elsif selected
812
+ items.map { |ret, txt| [ret, txt, selected == ret] }
813
+ else
814
+ items.map { it << false }
815
+ end
816
+ ).select(abortable)
817
+ ensure
818
+ temp&.end
819
+ end
820
+
821
+ #
822
+ # @!endgroup
1059
823
  #
1060
- # @return (see section)
1061
- def temporary(*text, **options, &block)
1062
- __with(Temporary.new(self), *text, **options, &block)
824
+ # @!group Utilities
825
+ #
826
+
827
+ # Executes a shell command and prints its output to the terminal.
828
+ #
829
+ # All arguments and options are forwarded to `Terminal.sh`, which in turn
830
+ # uses `Process.spawn`.
831
+ #
832
+ # @example Run a simple command
833
+ # ui.sh 'echo "Hello Ruby!"'
834
+ #
835
+ # @example Pipe a string as stdin
836
+ # ui.sh 'cat', input: 'Hello from stdin'
837
+ #
838
+ # @overload sh(*cmd, env = {}, shell: false, input: nil, **spawn_options)
839
+ # @param cmd [#to_s, ...] command and arguments, same as `Process.spawn`
840
+ # @param env [Hash, nil] additional environment variables
841
+ # @param shell [Boolean] when `true` runs the command through a system shell
842
+ # @param input piped standard input; accepts any object with `#readpartial`,
843
+ # `#to_io`, `#each`, `#to_a`, or anything `IO.write` accepts (e.g. a String)
844
+ # @param spawn_options [Hash] additional options forwarded to `Process.spawn`
845
+ # @return (see #puts)
846
+ def sh(...) = Shell.render(self, ...)
847
+
848
+ # Executes a shell command, captures its output, and returns it.
849
+ #
850
+ # Stdout and stderr lines are displayed in a scrolling region limited to
851
+ # `max_lines`. All other arguments are identical to {#sh}.
852
+ #
853
+ # @example Capture and inspect output
854
+ # status, out, err = ui.run 'ls', '-la'
855
+ # ui.puts "exit #{status.exitstatus}"
856
+ #
857
+ # @example Limit displayed lines and pipe input
858
+ # File.open('data.txt') { |f| ui.run 'wc', '-l', input: f, max_lines: 5 }
859
+ #
860
+ # @overload run(*cmd, env = {}, shell: false, input: nil, max_lines: 10, **spawn_options)
861
+ # @param cmd (see #sh)
862
+ # @param max_lines [#to_int] maximum number of output lines shown at once
863
+ # @param env (see #sh)
864
+ # @param shell (see #sh)
865
+ # @param input (see #sh)
866
+ # @param spawn_options (see #sh)
867
+ # @return [Array(Process::Status, Array<String>, Array<String>)]
868
+ # three-element array of exit status, stdout lines, and stderr lines
869
+ # @return [nil] when the command could not be started
870
+ def run(*, max_lines: 10, **)
871
+ (Terminal.ansi? ? ShellRunner : DumbShellRunner).render(
872
+ self,
873
+ max_lines,
874
+ *,
875
+ **
876
+ )
1063
877
  end
1064
878
 
1065
879
  #
1066
880
  # @!endgroup
1067
881
  #
1068
882
 
1069
- private
883
+ # @private
884
+ def columns = Terminal.columns
1070
885
 
1071
- def __with(...) = NattyUI.__send__(:_with, ...)
886
+ private
1072
887
 
1073
- def __sec(color, mark, title, text, options, &block)
1074
- if title && !title.empty?
1075
- title, *rest =
1076
- Text.each_line(title, limit: columns - 9, ansi: Terminal.ansi?).to_a
1077
- text.unshift(rest.join("\n")) unless rest.empty?
1078
- end
1079
- __with(Section.new(self, color, mark, title), *text, **options, &block)
888
+ def __determine_max_width(value)
889
+ return columns unless value
890
+ return 0 if value == 0
891
+ return value.to_int + columns if value < 0
892
+ return (value * columns).round if value < 1
893
+ [value.to_int, columns].min
1080
894
  end
1081
895
 
1082
- def __await(yes, no)
1083
- Terminal.on_key_event do |event|
1084
- event = event.name
1085
- if (no == event) || (no.is_a?(Enumerable) && no.include?(event))
1086
- return false
1087
- end
1088
- if (yes == event) || (yes.is_a?(Enumerable) && yes.include?(event))
1089
- return true
1090
- end
1091
- true
896
+ def __with(element)
897
+ NattyUI.__send__(:_begin, element)
898
+ return element unless block_given?
899
+ begin
900
+ yield(element)
901
+ ensure
902
+ NattyUI.__send__(:_end, element)
1092
903
  end
1093
904
  end
1094
905
  end
1095
906
 
1096
907
  dir = __dir__
1097
908
 
1098
- autoload :Framed, "#{dir}/framed.rb"
909
+ # @comment Elements:
910
+ autoload :Frame, "#{dir}/frame.rb"
911
+ autoload :Margin, "#{dir}/margin.rb"
912
+ autoload :Progress, "#{dir}/progress.rb"
913
+ autoload :DumbProgress, "#{dir}/dumb_progress.rb"
1099
914
  autoload :Section, "#{dir}/section.rb"
1100
- autoload :Table, "#{dir}/table.rb"
1101
915
  autoload :Task, "#{dir}/task.rb"
1102
916
  autoload :Temporary, "#{dir}/temporary.rb"
1103
- autoload :Theme, "#{dir}/theme.rb"
1104
- autoload :Utils, "#{dir}/utils.rb"
1105
- private_constant(:Framed, :Utils)
1106
917
 
1107
- autoload :Choice, "#{dir}/choice.rb"
1108
- autoload :DumbChoice, "#{dir}/dumb_choice.rb"
1109
- autoload :Options, "#{dir}/options.rb"
1110
- autoload :DumbOptions, "#{dir}/dumb_options.rb"
1111
- autoload :Progress, "#{dir}/progress.rb"
1112
- autoload :DumbProgress, "#{dir}/progress.rb"
918
+ # @comment Helper:
919
+ autoload :Table, "#{dir}/helper/table.rb"
920
+
921
+ # @comment Utils:
922
+ autoload :Utils, "#{dir}/utils/utils.rb"
923
+ autoload :StrConst, "#{dir}/utils/str_const.rb"
924
+ private_constant(:Utils, :StrConst)
925
+
926
+ # @comment Renderer:
927
+ autoload :VBars, "#{dir}/renderer/bars.rb"
928
+ autoload :HBars, "#{dir}/renderer/bars.rb"
929
+ autoload :Choice, "#{dir}/renderer/choice.rb"
930
+ autoload :DumbChoice, "#{dir}/renderer/dumb_choice.rb"
931
+ autoload :Heading, "#{dir}/renderer/heading.rb"
932
+ autoload :HorizontalRule, "#{dir}/renderer/horizontal_rule.rb"
933
+ autoload :LS, "#{dir}/renderer/ls.rb"
934
+ autoload :CompactLS, "#{dir}/renderer/ls.rb"
935
+ autoload :Mark, "#{dir}/renderer/mark.rb"
936
+ autoload :Quote, "#{dir}/renderer/quote.rb"
937
+ autoload :Select, "#{dir}/renderer/select.rb"
938
+ autoload :DumbSelect, "#{dir}/renderer/dumb_select.rb"
939
+ autoload :Shell, "#{dir}/renderer/shell.rb"
940
+ autoload :ShellRunner, "#{dir}/renderer/shell_runner.rb"
941
+ autoload :DumbShellRunner, "#{dir}/renderer/dumb_shell_runner.rb"
942
+ autoload :TableRenderer, "#{dir}/renderer/table_renderer.rb"
1113
943
  private_constant(
944
+ :VBars,
945
+ :HBars,
1114
946
  :Choice,
1115
947
  :DumbChoice,
1116
- :Options,
1117
- :DumbOptions,
1118
- :Progress,
1119
- :DumbProgress
1120
- )
1121
-
1122
- autoload :CompactLSRenderer, "#{dir}/ls_renderer.rb"
1123
- autoload :HBarsRenderer, "#{dir}/hbars_renderer.rb"
1124
- autoload :LSRenderer, "#{dir}/ls_renderer.rb"
1125
- autoload :ShellRenderer, "#{dir}/shell_renderer.rb"
1126
- autoload :VBarsRenderer, "#{dir}/vbars_renderer.rb"
1127
- private_constant(
1128
- :CompactLSRenderer,
1129
- :HBarsRenderer,
1130
- :LSRenderer,
1131
- :ShellRenderer,
1132
- :VBarsRenderer
948
+ :Heading,
949
+ :HorizontalRule,
950
+ :LS,
951
+ :CompactLS,
952
+ :Mark,
953
+ :Quote,
954
+ :Select,
955
+ :DumbSelect,
956
+ :Shell,
957
+ :ShellRunner,
958
+ :DumbShellRunner,
959
+ :TableRenderer
1133
960
  )
1134
961
  end