natty-ui 0.35.0 → 1.0.2

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 -949
  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 +429 -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 +62 -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 +76 -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,956 @@
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 StrConst === prefix
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 StrConst === cprefix
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 StrConst === suffix
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 StrConst === csuffix
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
+ if cprefix || csuffix
105
+ lines.map! do |line|
106
+ line = "#{cprefix}#{line}#{csuffix}"
107
+ cprefix, prefix = prefix, nil if prefix
108
+ csuffix, suffix = suffix, nil if suffix
109
+ line
167
110
  end
168
111
  end
112
+ Terminal.puts(*lines, bbcode: false)
113
+ @lines_written += lines.size
169
114
  self
170
115
  end
171
116
 
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)
184
- end
185
-
186
- # Print given text as lines like {#puts}. Used in elements with temporary
187
- # output like {#task} the text will be kept ("pinned").
188
- #
189
- # It can optionally have a decoration marker in first line like {#mark}.
117
+ # Prints one or more blank lines.
190
118
  #
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.
119
+ # @example Print a single blank line
120
+ # ui.space
200
121
  #
201
- # @param (see #puts)
202
- # @param mark (see #mark)
203
- # @option (see #puts)
122
+ # @example Print three blank lines
123
+ # ui.space 3
204
124
  #
205
- # @return (see puts)
206
- def pin(*text, mark: nil, **options)
207
- mark(*text, mark: mark, pin: true, **options)
208
- end
209
-
210
- # Print given text as a quotation.
211
- #
212
- # @param text (see puts)
213
- #
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.
227
- #
228
- # There are specific shortcuts for heading levels:
229
- # {#h1}, {#h2}, {#h3}, {#h4}, {#h5}, {#h6}.
230
- #
231
- # @example Print a level 1 heading
232
- # ui.heading(1, 'This is a H1 heading element')
233
- # # => ╴╶╴╶─═══ This is a H1 heading element ═══─╴╶╴╶
234
- #
235
- # @param level [#to_i]
236
- # heading level, one of 1..6
237
- # @param text (see puts)
238
- #
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
- )
125
+ # @param count [#to_int] number of blank lines to print
126
+ # @return (see #puts)
127
+ def space(count = 1)
128
+ (count = count.to_int) < 1 ? self : puts(" \n" * count)
251
129
  end
252
130
 
253
- # Print given text as a H1 {#heading}.
131
+ # Prints text with a leading mark symbol.
254
132
  #
255
- # @param text (see puts)
133
+ # @example Default mark
134
+ # ui.mark 'Item one'
256
135
  #
257
- # @return (see puts)
258
- def h1(*text) = heading(1, *text)
259
-
260
- # Print given text as a H2 {#heading}.
136
+ # @example Named symbol mark
137
+ # ui.mark 'Done!', mark: :checkmark
261
138
  #
262
- # @param text (see puts)
139
+ # @example Custom string mark
140
+ # ui.mark 'Warning', mark: '[bright_red]!'
263
141
  #
264
- # @return (see puts)
265
- def h2(*text) = heading(2, *text)
142
+ # @param text (see #puts)
143
+ # @param mark [Symbol, #to_s] mark to use as prefix:
144
+ # - `:default` — blue bullet •
145
+ # - `:checkmark` — green check mark ✓
146
+ # - `:item` — dim bullet •
147
+ # - any other object — converted via `#to_s` and used as the literal prefix
148
+ # @param popts [Hash] a customizable set of print options, see {#puts}
149
+ # @return (see #puts)
150
+ def mark(*text, mark: :default, **popts)
151
+ Mark.render(self, mark, *text, **popts)
152
+ end
266
153
 
267
- # Print given text as a H3 {#heading}.
154
+ # Prints text with a green check-mark prefix.
155
+ #
156
+ # Shorthand for `mark(*text, mark: :checkmark, **options)`.
268
157
  #
269
- # @param text (see puts)
158
+ # @example
159
+ # ui.ok 'All tests passed'
270
160
  #
271
- # @return (see puts)
272
- def h3(*text) = heading(3, *text)
161
+ # @param text (see #puts)
162
+ # @param popts (see #mark)
163
+ # @return (see #puts)
164
+ def ok(*text, **popts) = mark(*text, mark: :checkmark, **popts)
273
165
 
274
- # Print given text as a H4 {#heading}.
166
+ # Prints text with a mark that persists after a {Temporary} element closes.
275
167
  #
276
- # @param text (see puts)
168
+ # Identical to {#mark} but survives when the surrounding {Temporary} element
169
+ # (see {#temporary}, {#task}, {#progress}) is erased.
277
170
  #
278
- # @return (see puts)
279
- def h4(*text) = heading(4, *text)
171
+ # @example Pin a success note inside a task
172
+ # ui.task 'Deploy' do
173
+ # do_deploy
174
+ # ui.pin 'Deployed to production', mark: :checkmark
175
+ # end
176
+ #
177
+ # @param (see mark)
178
+ # @return (see #puts)
179
+ def pin(*text, mark: :default, **popts)
180
+ mark(*text, mark:, pin: true, **popts)
181
+ end
280
182
 
281
- # Print given text as a H5 {#heading}.
183
+ # Prints text with a left-side quotation border.
282
184
  #
283
- # @param text (see puts)
185
+ # @example
186
+ # ui.quote "To be or not to be,\nthat is the question."
284
187
  #
285
- # @return (see puts)
286
- def h5(*text) = heading(5, *text)
188
+ # @param (see #puts)
189
+ # @return (see #puts)
190
+ def quote(*text) = Quote.render(self, *text)
287
191
 
288
- # Print given text as a H6 {#heading}.
192
+ # Prints a heading at the given level.
289
193
  #
290
- # @param text (see puts)
194
+ # Six heading levels are supported (similarly to HTML `<h1>`–`<h6>`).
195
+ # Use the shorthand helpers {#h1}–{#h6} for convenience.
291
196
  #
292
- # @return (see puts)
293
- def h6(*text) = heading(6, *text)
294
-
295
- # Print a horizontal rule.
197
+ # @example Large heading
198
+ # ui.heading 1, 'Chapter One'
296
199
  #
297
- # @example Print double line
298
- # ui.hr :double
200
+ # @example Sub-heading
201
+ # ui.heading 3, 'Section [i]Overview[/i]'
299
202
  #
300
- # @param type [Symbol]
301
- # border type
203
+ # @param level [#to_int] heading level, `1` (largest) to `6` (smallest)
204
+ # @param title [#to_s] heading text
205
+ # @return (see #puts)
206
+ def heading(level, title) = Heading.render(self, level, title)
207
+
208
+ # Prints a level-1 heading.
302
209
  #
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
210
+ # @param title (see #heading)
211
+ # @return (see #puts)
212
+ def h1(title) = heading(1, title)
309
213
 
310
- # Print one or more space lines.
214
+ # Prints a level-2 heading.
311
215
  #
312
- # @param count [#to_i]
313
- # lines to print
216
+ # @param title (see #heading)
217
+ # @return (see #puts)
218
+ def h2(title) = heading(2, title)
219
+
220
+ # Prints a level-3 heading.
314
221
  #
315
- # @return (see puts)
316
- def space(count = 1)
317
- (count = count.to_i).positive? ? puts("\n" * count) : self
318
- end
222
+ # @param title (see #heading)
223
+ # @return (see #puts)
224
+ def h3(title) = heading(3, title)
319
225
 
320
- # Print given items as list (like 'ls' command).
226
+ # Prints a level-4 heading.
321
227
  #
322
- # Each list item will optionally be decorated with the given glyph as:
228
+ # @param title (see #heading)
229
+ # @return (see #puts)
230
+ def h4(title) = heading(4, title)
231
+
232
+ # Prints a level-5 heading.
323
233
  #
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
234
+ # @param title (see #heading)
235
+ # @return (see #puts)
236
+ def h5(title) = heading(5, title)
237
+
238
+ # Prints a level-6 heading.
328
239
  #
329
- # @example Print all Ruby files as a numbered list
330
- # ui.ls Dir['*/**/*.rb'], glyph: 1
240
+ # @param title (see #heading)
241
+ # @return (see #puts)
242
+ def h6(title) = heading(6, title)
243
+
244
+ # Prints a horizontal rule spanning the available width.
331
245
  #
332
- # @example Print all Ruby files as a bullet point list (with green bullets)
333
- # ui.ls Dir['*/**/*.rb'], glyph: '[green]•[/fg]'
246
+ # @example Default rule
247
+ # ui.hr
334
248
  #
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
249
+ # @example Named style
250
+ # ui.hr :double
341
251
  #
342
- # @return (see puts)
252
+ # @example Repeated string
253
+ # ui.hr '+-'
254
+ #
255
+ # @param kind [Symbol, #to_s, nil]
256
+ # - `nil` — default style
257
+ # - `Symbol` — named style: `:single`, `:double`, `:heavy`
258
+ # - `#to_s` — string repeated to fill the available width
259
+ # @return (see #puts)
260
+ def hr(kind = nil) = HorizontalRule.render(self, kind)
261
+
262
+ # Prints a multi-column list.
263
+ #
264
+ # Items are arranged in columns to fit the terminal width.
265
+ #
266
+ # @example Plain list
267
+ # ui.ls 'Alice', 'Bob', 'Carol'
268
+ #
269
+ # @example Hex-numbered list
270
+ # ui.ls 'red', 'green', 'blue', glyph: :hex
271
+ #
272
+ # @param items [#to_s, ...] items to list; nested arrays are flattened
273
+ # @param compact [Boolean] `true` = ordered in columns,
274
+ # `false` = ordered left to right per row
275
+ # @param glyph prefix applied to every item:
276
+ # - `nil` / `false` — no prefix
277
+ # - `Integer` — decimal counter starting at the given number
278
+ # - `Symbol` — alphabetic sequence starting at the given symbol
279
+ # (e.g. `:a` → `a`, `b`, `c`, …)
280
+ # - `:hex` — zero-based hex counter (`01`, `02`, …)
281
+ # - a `#to_s` value matching a hex value — hex counter at the given
282
+ # offset (e.g. `"0x0a"` starts at `0a`)
283
+ # - any other `#to_s` value — used as a literal prefix for every item
284
+ # @return (see #puts)
343
285
  def ls(*items, compact: true, glyph: nil)
344
286
  return self if items.empty?
345
- renderer = compact ? CompactLSRenderer : LSRenderer
346
- puts(*renderer.lines(items, glyph, columns))
347
- end
348
-
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) } }
287
+ puts(*(compact ? CompactLS : LS).lines(columns, items, glyph))
439
288
  end
440
289
 
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)
290
+ # Prints a vertical bar chart.
291
+ #
292
+ # All values must be non-negative.
293
+ #
294
+ # @example Simple bar chart
295
+ # ui.vbars [3, 7, 2, 9, 5]
296
+ #
297
+ # @example Normalised chart with custom height
298
+ # ui.vbars [10, 40, 25, 60], normalize: true, height: 15
299
+ #
300
+ # @param values [#each]
301
+ # collection of non-negative `Numeric` values
302
+ # @param min [Numeric, nil]
303
+ # lower bound for scaling; `nil` uses the data minimum
304
+ # @param max [Numeric, nil]
305
+ # upper bound for scaling; `nil` uses the data maximum
306
+ # @param normalize [Boolean]
307
+ # when `true` applies min-max normalization;
308
+ # when `false` scales each bar relative to the maximum value
309
+ # @param height [#to_int]
310
+ # chart height in lines (minimum 3; default: 10)
311
+ # @param bar_width [#to_int, :auto]
312
+ # width of each bar in characters; `:auto` fits the graph in available
313
+ # terminal width
314
+ # @param style [Symbol, String, Array<Style>, Array<String>, nil]
315
+ # ANSI style applied to the bars
316
+ # @return (see #puts)
458
317
  def vbars(
459
318
  values,
319
+ min: nil,
320
+ max: nil,
460
321
  normalize: false,
461
322
  height: 10,
462
- bar_width: :auto,
323
+ bar_width: 3,
463
324
  style: nil
464
325
  )
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
- )
326
+ bars = VBars.new(values, min, max, normalize)
327
+ return self if bars.empty?
328
+ raise(ArgumentError, 'values can not be negative') unless bars.valid?
329
+ puts(*bars.lines(columns, height, bar_width, style), bbcode: false)
479
330
  end
480
331
 
481
- # Dump given values as horizontal bars.
482
- #
483
- # @example Draw green bars
484
- # ui.hbars 1..10, style: :green
332
+ # Prints a horizontal bar chart.
485
333
  #
486
- # @example Draw bars in half sreen width
487
- # ui.hbars 1..10, style: :blue, width: 0.5
334
+ # All values must be non-negative.
488
335
  #
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
336
+ # @example Simple horizontal chart
337
+ # ui.hbars [3, 7, 2, 9, 5]
497
338
  #
498
- # @raise [ArgumentError] if any value is negative
339
+ # @example Chart without value labels
340
+ # ui.hbars [10, 40, 25, 60], text: false, normalize: true
499
341
  #
500
- # @return (see puts)
342
+ # @param values (see #vbars)
343
+ # @param min (see #vbars)
344
+ # @param max (see #vbars)
345
+ # @param normalize (see #vbars)
346
+ # @param style (see #vbars)
347
+ # @param width [#to_int, Float, :auto] maximum bar width in characters;
348
+ # a `Float` is a fraction of available width; `:auto` fills available width
349
+ # @param text [Boolean] when `true` prints the numeric value next to each bar
350
+ # @param text_style [Symbol, String, Array<Style>, Array<String>, nil]
351
+ # ANSI style applied to the value labels
352
+ # @return (see #puts)
501
353
  def hbars(
502
354
  values,
503
355
  min: nil,
504
356
  max: nil,
505
357
  normalize: false,
506
- text: true,
507
358
  width: :auto,
508
359
  style: nil,
360
+ text: true,
509
361
  text_style: nil
510
362
  )
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))
363
+ bars = HBars.new(values, min, max, normalize)
364
+ return self if bars.empty?
365
+ raise(ArgumentError, 'values can not be negative') unless bars.valid?
366
+ bars.with_text(text_style) if text
367
+ puts(*bars.lines(columns, width, style), bbcode: false)
519
368
  end
520
369
 
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
370
+ # Renders a {Table} to the terminal.
537
371
  #
538
- # @example Display simple progress
539
- # progress = ui.progress 'Check some stuff'
540
- # 10.times do
541
- # # simulate some work
542
- # sleep 0.1
372
+ # The method yields a {Table} object for population; if no block is given
373
+ # nothing is rendered.
543
374
  #
544
- # # here we actualize the progress
545
- # progress.step
375
+ # Border name symbols:
376
+ # `:rounded` (default), `:single`, `:double`, `:heavy`,
377
+ # `:single_double`, `:double_single`, `:single_heavy`, `:heavy_single`
378
+ #
379
+ # @example Simple table with a double outer frame
380
+ # ui.table border_frame: :double do |t|
381
+ # t.add_row '[b]Name', '[b]Score', align: :center
382
+ # t.add_row 'Alice', 42
383
+ # t.add_row 'Bob', 17
546
384
  # 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
581
- )
385
+ #
386
+ # @option options [:default, :none, :single, :double, :heavy] :border (:default)
387
+ # default border used for all border parts
388
+ # @option options [nil, :none, :single, :double, :heavy] :border_frame
389
+ # outer frame; falls back to `:border`
390
+ # @option options [nil, :none, :single, :double, :heavy] :border_vertical
391
+ # vertical column separators; falls back to `:border`
392
+ # @option options [nil, :none, :single, :double, :heavy] :border_horizontal
393
+ # horizontal row separators; falls back to `:border`
394
+ # @option options [Symbol, String, Array<Style>, Array<String>, nil] :border_style
395
+ # ANSI style applied to all borders
396
+ # @option options [Symbol, Array<Symbol>] :frame (:all)
397
+ # which frame sides to draw: `:all` or an enumerable of
398
+ # `:top`, `:right`, `:bottom`, `:left`
399
+ # @option options [#to_int, Float, :max, nil] :width
400
+ # `:max` expands to the full terminal width;
401
+ # a negative `#to_int` is an offset from the terminal width;
402
+ # a `Float` is a fraction of the terminal width
403
+ # @yield [table] a {Table} instance to populate with rows and cells
404
+ # @yieldparam table [Table] the table
405
+ # @return [Features]
406
+ def table(**options)
407
+ return self unless block_given?
408
+ yield(table = Table.new)
409
+ return self if table.empty?
410
+ puts(*TableRenderer.lines(columns, table, **options), bbcode: false)
582
411
  end
583
412
 
584
- # Execute a program.
585
413
  #
586
- # @example Execute a simple command
587
- # ui.sh 'ls'
414
+ # @!endgroup
588
415
  #
589
- # @example Execute a command wih arguments
590
- # ret = ui.sh('curl', '--version')
591
- # raise('Curl not found') unless ret&.success?
416
+
592
417
  #
593
- # @example Execute shell commands
594
- # ui.sh "mkdir 'test' && cd 'test'"
418
+ # @!group Section elements
595
419
  #
596
- # @example Execute a command with environment variables
597
- # ui.sh({'ENV' => 'test'}, 'rails')
420
+
421
+ # Creates a {Temporary} element whose output is erased when it closes.
598
422
  #
599
- # @see run
423
+ # @example Manual close
424
+ # tmp = ui.temporary
425
+ # ui.puts 'Loading…'
426
+ # sleep 1
427
+ # tmp.end # erases "Loading…"
600
428
  #
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
429
+ # @example Block form
430
+ # ui.temporary do
431
+ # ui.puts 'Thinking…'
432
+ # sleep 2
433
+ # end # "Thinking…" is erased here
434
+ #
435
+ # @yield [temp] the {Temporary} element
436
+ # @yieldparam temp [Temporary]
437
+ # @return [Object] return value of the block
438
+ # @return [Temporary] itself, if no block is specified
439
+ def temporary(&) = __with(Temporary.new(self), &)
644
440
 
441
+ # Creates a {Margin} element that adds horizontal and vertical whitespace.
645
442
  #
646
- # @!endgroup
443
+ # @example
444
+ # ui.margin 0, 0.25 do
445
+ # ui.puts 'This text has 25% width horizontal margin.'
446
+ # end
647
447
  #
648
-
448
+ # @overload margin(value)
449
+ # Margin for all sides.
450
+ # @param value [#to_int, Float] margin of all four sides
451
+ #
452
+ # @overload margin(vertical = 0, horizontal = 1)
453
+ # Seperate vertical and horizontal margin.
454
+ # @param vertical [#to_int] top and bottom margin
455
+ # @param horizontal [#to_int, Float] left and right margin
456
+ #
457
+ # @overload margin(top, horizontal, bottom)
458
+ # Seperate top, bottom and horizontal margin.
459
+ # @param top [#to_int] top margin
460
+ # @param horizontal [#to_int, Float] left and right margin
461
+ # @param bottom [#to_int] bottom margin
462
+ #
463
+ # @overload margin(top, right, bottom, left)
464
+ # Seperate margins.
465
+ # @param top [#to_int] top margin
466
+ # @param right [#to_int, Float] right margin
467
+ # @param bottom [#to_int] bottom margin
468
+ # @param left [#to_int, Float] left margin
469
+ #
470
+ # @overload margin(top: 0, right: 0, bottom: 0, left: 0)
471
+ # Specific margin.
472
+ # @param top [#to_int] top margin
473
+ # @param right [#to_int, Float] right margin
474
+ # @param bottom [#to_int] bottom margin
475
+ # @param left [#to_int, Float] left margin
476
+ #
477
+ # @yield [margin] the {Margin} element
478
+ # @yieldparam margin [Margin]
479
+ # @return [Object] return value of the block
480
+ # @return [Margin] itself, if no block is specified
481
+ def margin(*, &) = __with(Margin.new(self, *), &)
482
+
483
+ # Creates a {Section} element — a bordered container with an optional title.
484
+ #
485
+ # @example Manual close
486
+ # sec = ui.section 'Results'
487
+ # ui.ok 'All good'
488
+ # sec.end
489
+ #
490
+ # @example Block form with title
491
+ # ui.section 'Summary' do
492
+ # ui.puts '3 files processed.'
493
+ # end
649
494
  #
650
- # @!group Sub-Elements
495
+ # @example Block form without title
496
+ # ui.section do
497
+ # ui.puts 'Anonymous section content.'
498
+ # end
651
499
  #
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)
500
+ # @param title [#to_s, nil]
501
+ # optional header text
502
+ # @param type [:default, :message, :information, :warning, :error, :fatal]
503
+ # visual style of the section
504
+ # @param border [:default, :rounded, :single, :double, :heavy]
505
+ # border style
506
+ # @yield [section] the {Section} element
507
+ # @yieldparam section [Section]
508
+ # @return [Object] return value of the block
509
+ # @return [Section] itself, if no block is specified
510
+ def section(title = nil, type: :default, border: :default, &)
511
+ __with(Section.new(self, title, type, border), &)
696
512
  end
697
513
  alias begin section
698
514
 
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)
515
+ # Creates a {Section} with `:message` styling.
516
+ #
517
+ # @param title [#to_s] header text
518
+ # @param border (see #section)
519
+ # @yield (see #section)
520
+ # @yieldparam (see #section)
521
+ # @return (see #section)
522
+ def message(title, border: :default, &)
523
+ section(title, border:, type: :message, &)
725
524
  end
726
525
  alias msg message
727
526
 
728
- # Create a visually separated section marked as informational with title for
729
- # the output of text elements.
527
+ # Creates a {Section} with `:information` styling.
730
528
  #
731
- # @macro msg_like
732
- def information(title, *text, **options, &block)
733
- __sec(:information, true, title, text, options, &block)
529
+ # @param (see #message)
530
+ # @yield (see #message)
531
+ # @yieldparam (see #message)
532
+ # @return (see #message)
533
+ def information(title, border: :default, &)
534
+ section(title, border:, type: :information, &)
734
535
  end
735
536
  alias info information
736
537
 
737
- # Create a visually separated section marked as warning with title for
738
- # the output of text elements.
538
+ # Creates a {Section} with `:warning` styling.
739
539
  #
740
- # @macro msg_like
741
- def warning(title, *text, **options, &block)
742
- __sec(:warning, true, title, text, options, &block)
540
+ # @param (see #message)
541
+ # @yield (see #message)
542
+ # @yieldparam (see #message)
543
+ # @return (see #message)
544
+ def warning(title, border: :default, &)
545
+ section(title, border:, type: :warning, &)
743
546
  end
744
547
  alias warn warning
745
548
 
746
- # Create a visually separated section marked as error with title for
747
- # the output of text elements.
549
+ # Creates a {Section} with `:error` styling.
748
550
  #
749
- # @macro msg_like
750
- def error(title, *text, **options, &block)
751
- __sec(:error, true, title, text, options, &block)
551
+ # @param (see #message)
552
+ # @yield (see #message)
553
+ # @yieldparam (see #message)
554
+ # @return (see #message)
555
+ def error(title, border: :default, &)
556
+ section(title, border:, type: :error, &)
752
557
  end
753
- alias err error
754
558
 
755
- # Create a visually separated section marked as failure with title for
756
- # the output of text elements.
559
+ # Creates a {Section} with `:fatal` styling.
757
560
  #
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
- )
561
+ # @param (see #message)
562
+ # @yield (see #message)
563
+ # @yieldparam (see #message)
564
+ # @return (see #message)
565
+ def fatal(title, border: :default, &)
566
+ section(title, border:, type: :fatal, &)
813
567
  end
814
568
 
815
- # Generate a task section.
569
+ # Creates a {Frame} element — a bordered box with an optional title.
570
+ #
571
+ # @example Manual close
572
+ # frm = ui.frame 'Preview'
573
+ # ui.puts 'Content inside the frame.'
574
+ # frm.end
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 Block form with title and custom border
577
+ # ui.frame 'Results', border: :double do
578
+ # ui.puts 'All checks passed.'
579
+ # end
824
580
  #
825
- # @yieldparam task [Task] itself
581
+ # @example Block form without a title
582
+ # ui.frame do
583
+ # ui.puts 'Framed content.'
584
+ # end
826
585
  #
827
- # @return [Task] itself
828
- def task(title, *text, pin: false, **options, &block)
829
- __with(Task.new(self, title, pin), *text, **options, &block)
586
+ # @param title (see #section)
587
+ # @param border (see #section)
588
+ # @param style [Symbol, String, Array<Style>, Array<String>, nil]
589
+ # ANSI style applied to the frame
590
+ # @yield [frame] the {Frame} element
591
+ # @yieldparam frame [Frame]
592
+ # @return [Object] return value of the block
593
+ # @return [Frame] itself, if no block is specified
594
+ def frame(title = nil, border: :default, style: nil, &)
595
+ __with(Frame.new(self, title, border, style), &)
830
596
  end
831
597
 
598
+ # Creates a {Task} element — a labelled step that shows a spinner while
599
+ # running and a check mark on success.
832
600
  #
833
- # @!endgroup
834
- #
835
-
601
+ # @example Manual close
602
+ # t = ui.task 'Installing dependencies'
603
+ # run_install
604
+ # t.end
836
605
  #
837
- # @!group User Interaction
606
+ # @example Block form
607
+ # ui.task 'Installing dependencies' do
608
+ # run_install
609
+ # end
838
610
  #
611
+ # @param title [#to_s] task description
612
+ # @param pin [Boolean] whether the task title should be pinned
613
+ # @yield [task] the {Task} element
614
+ # @yieldparam task [Task]
615
+ # @return [Object] return value of the block
616
+ # @return [Task] itself, if no block is specified
617
+ def task(title, pin: false, &) = __with(Task.new(self, title, pin), &)
839
618
 
840
- # Wait for user input.
619
+ # Creates a {Progress} element for tracking incremental work.
841
620
  #
842
- # @example Wait until user wants to coninue
843
- # ui.await { ui.puts '[faint][\\Press ENTER to continue...][/faint]' }
621
+ # When `max` is given the progress is displayed as a percentage bar.
622
+ # When `max` is `nil` an open-ended dot animation is shown instead.
844
623
  #
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?'
624
+ # @example Bounded progress
625
+ # ui.progress 'Processing', max: items.size do |bar|
626
+ # items.each { process(it); bar.step }
848
627
  # 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
- #
857
- # @overload await(yes: 'Enter', no: 'Esc', &block)
858
- # @yieldparam temp [Temporary]
859
- # temporary displayed section (section will be erased after input)
860
628
  #
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
629
+ # @example Open-ended progress
630
+ # ui.progress 'Working…' do |bar|
631
+ # loop { bar.step; break if done? }
632
+ # end
865
633
  #
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
634
+ # @param title (see #section)
635
+ # @param max [Numeric, nil] maximum value
636
+ # @param popts (see #mark)
637
+ # @yield [progress] the {Progress} element
638
+ # @yieldparam progress [Progress]
639
+ # @return [Object] return value of the block
640
+ # @return [Progress] itself when no block is given
641
+ def progress(*title, max: nil, **popts, &)
642
+ __with(
643
+ (Terminal.ansi? ? Progress : DumbProgress).new(
644
+ self,
645
+ max,
646
+ *title,
647
+ **popts
648
+ ),
649
+ &
650
+ )
876
651
  end
877
652
 
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
653
+ #
654
+ # @!endgroup
655
+ #
656
+ # @!group User Interaction
657
+ #
962
658
 
963
- # Allows the user to select from several options.
964
- # All options are returned with their selection status.
659
+ # Waits for the user to press any key.
965
660
  #
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
661
+ # @example Plain wait
662
+ # ui.puts 'Press any key to continue…'
663
+ # ui.await
972
664
  #
973
- # @yieldparam temp [Temporary]
974
- # temporary displayed section (section will be erased after input)
665
+ # @example With temporary prompt
666
+ # ui.await { ui.puts '[faint]Press any key to continue…' }
975
667
  #
976
- # @return [{#to_s => [true,false]}]
977
- # Hash of options and their selection state
668
+ # @yield (see #temporary)
669
+ # @yieldparam (see #temporary)
978
670
  # @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) }
671
+ def await
672
+ yield(temp = temporary) if block_given?
673
+ Terminal.read_key_event
674
+ nil
675
+ ensure
676
+ temp&.end
989
677
  end
990
678
 
991
- # Allows the user to select from several options.
992
- # The selected options are returned.
679
+ # Waits for a key event and returns information about it.
993
680
  #
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
681
+ # Key names are strings such as `"a"`, `"Enter"`, `"Esc"`, `"Back"`,
682
+ # `"Shift+Alt+F1"`.
998
683
  #
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)
684
+ # @example
685
+ # answer = ui.query yes: 'y', no: 'n'
686
+ # ui.puts answer == :yes ? 'Confirmed!' : 'Cancelled.'
1007
687
  #
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]
688
+ # @example With a temporary prompt
689
+ # answer = ui.query(yes: 'y', no: 'n') do
690
+ # ui.puts 'Continue? ([b]y[/b]/[b]n[/b])'
691
+ # end
692
+ #
693
+ # @param options [Hash<Object => String, #each>]
694
+ # map of return values to key names or enumerables of key names;
695
+ # e.g. `{ yes: 'y', no: %w[n Esc] }`
696
+ # @yield (see #temporary)
697
+ # @yieldparam (see #temporary)
698
+ # @return [Object] matched option key
699
+ def query(**options)
700
+ yield(temp = temporary) if block_given?
701
+ return Terminal.read_key_event.name if options.empty?
702
+ Terminal.on_key_event do |event|
703
+ event = event.name
704
+ found, =
705
+ options.find do |_, value|
706
+ (value.is_a?(Enumerable) && value.include?(event)) || value == event
707
+ end
708
+ break found if found
1019
709
  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 }
710
+ ensure
711
+ temp&.end
1026
712
  end
1027
713
 
714
+ # Presents a list of options and returns the one the user selects.
1028
715
  #
1029
- # @!endgroup
716
+ # In ANSI mode the user navigates with arrow keys and confirms with Enter.
717
+ # In dumb mode items are numbered and the user types the item number.
1030
718
  #
1031
-
719
+ # @example Positional items
720
+ # answer = ui.choice 'Yes', 'No', 'Cancel'
1032
721
  #
1033
- # @!group Utilities
722
+ # @example Positional items
723
+ # answer = ui.choice 'Yes', 'No', abortable: true do
724
+ # ui.puts 'Overwrite the file?'
725
+ # end
1034
726
  #
727
+ # @example Keyword pairs
728
+ # action = ui.choice(overwrite: 'Overwrite', skip: 'Skip') do
729
+ # ui.puts 'File already exists.'
730
+ # end
731
+ #
732
+ # @overload choice(*items, abortable: false, selected: nil)
733
+ # Items are passed as positional arguments; the selected item itself is
734
+ # returned.
735
+ # @param items [#to_s, ...] options to choose from
736
+ # @param abortable [Boolean] when `true` the user can press Esc to cancel
737
+ # @param selected pre-selected item value, or `nil`
738
+ #
739
+ # @overload choice(abortable: false, selected: nil, **pairs)
740
+ # Items are passed as keyword pairs `{ return_value => label }`; the
741
+ # matching key is returned.
742
+ # @param abortable [Boolean] when `true` the user can press Esc to cancel
743
+ # @param selected pre-selected return value, or `nil`
744
+ # @param pairs [Hash{Object => #to_s}] map of return values to display labels
745
+ #
746
+ # @yield (see #temporary)
747
+ # @yieldparam (see #temporary)
748
+ # @return [Object] the key of the selected pair, or `nil` if aborted
749
+ def choice(*items, abortable: false, selected: nil, **pairs)
750
+ return if items.empty? && pairs.empty?
751
+ yield(temp = temporary) if block_given?
752
+ (Terminal.ansi? ? Choice : DumbChoice).new(
753
+ self,
754
+ items + pairs.values,
755
+ Array.new(items.size, &:itself) + pairs.keys
756
+ ).select(abortable, selected)
757
+ ensure
758
+ temp&.end
759
+ end
1035
760
 
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.
761
+ # Presents a list of options and returns all items the user selects.
1041
762
  #
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
763
+ # In ANSI mode the user toggles items with Space and confirms with Enter.
764
+ # In dumb mode items are numbered and the user types item numbers.
1047
765
  #
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
766
+ # @example Positional items
767
+ # picks = ui.select 'Kitty', 'iTerm2', 'Ghostty'
768
+ #
769
+ # @example Positional items with all pre-selected
770
+ # picks = ui.select 'A', 'B', 'C', selected: :all do
771
+ # ui.puts 'Choose features to enable:'
1055
772
  # end
1056
773
  #
1057
- # @yieldparam temp [Temporary]
1058
- # itself
774
+ # @example Keyword pairs
775
+ # flags = ui.select verbose: 'Verbose', debug: 'Debug', trace: 'Trace'
776
+ #
777
+ # @overload select(*items, abortable: false, selected: nil)
778
+ # Items are passed as positional arguments; the selected items themselves
779
+ # are returned.
780
+ # @param items [#to_s, ...] options to choose from
781
+ # @param abortable [Boolean] when `true` the user can press Esc to cancel
782
+ # @param selected [nil, :all, #each]
783
+ # pre-selected items: `nil` = none, `:all` = all,
784
+ # or an enumerable of item values to pre-select
785
+ #
786
+ # @overload select(abortable: false, selected: nil, **pairs)
787
+ # Items are passed as keyword pairs `{ return_value => label }`; the
788
+ # matching keys are returned.
789
+ # @param abortable [Boolean] when `true` the user can press Esc to cancel
790
+ # @param selected [nil, :all, #each]
791
+ # @param pairs [Hash{Object => #to_s}] map of return values to labels
792
+ #
793
+ # @yield (see #temporary)
794
+ # @yieldparam (see #temporary)
795
+ # @return [Array] keys of selected pairs, or `nil` if aborted
796
+ def select(*items, abortable: false, selected: nil, **pairs)
797
+ return if items.empty? && pairs.empty?
798
+ yield(temp = temporary) if block_given?
799
+ items = items.to_h { [it, it] }.merge!(pairs)
800
+ (Terminal.ansi? ? Select : DumbSelect).new(
801
+ self,
802
+ if selected == :all
803
+ items.map { it << true }
804
+ elsif selected.is_a?(Enumerable)
805
+ items.map { |ret, txt| [ret, txt, selected.include?(ret)] }
806
+ elsif selected
807
+ items.map { |ret, txt| [ret, txt, selected == ret] }
808
+ else
809
+ items.map { it << false }
810
+ end
811
+ ).select(abortable)
812
+ ensure
813
+ temp&.end
814
+ end
815
+
816
+ #
817
+ # @!endgroup
1059
818
  #
1060
- # @return (see section)
1061
- def temporary(*text, **options, &block)
1062
- __with(Temporary.new(self), *text, **options, &block)
819
+ # @!group Utilities
820
+ #
821
+
822
+ # Executes a shell command and prints its output to the terminal.
823
+ #
824
+ # All arguments and options are forwarded to `Terminal.sh`, which in turn
825
+ # uses `Process.spawn`.
826
+ #
827
+ # @example Run a simple command
828
+ # ui.sh 'echo "Hello Ruby!"'
829
+ #
830
+ # @example Pipe a string as stdin
831
+ # ui.sh 'cat', input: 'Hello from stdin'
832
+ #
833
+ # @overload sh(*cmd, env = {}, shell: false, input: nil, **spawn_options)
834
+ # @param cmd [#to_s, ...] command and arguments, same as `Process.spawn`
835
+ # @param env [Hash, nil] additional environment variables
836
+ # @param shell [Boolean] when `true` runs the command through a system shell
837
+ # @param input piped standard input; accepts any object with `#readpartial`,
838
+ # `#to_io`, `#each`, `#to_a`, or anything `IO.write` accepts (e.g. a String)
839
+ # @param spawn_options [Hash] additional options forwarded to `Process.spawn`
840
+ # @return (see #puts)
841
+ def sh(...) = Shell.render(self, ...)
842
+
843
+ # Executes a shell command, captures its output, and returns it.
844
+ #
845
+ # Stdout and stderr lines are displayed in a scrolling region limited to
846
+ # `max_lines`. All other arguments are identical to {#sh}.
847
+ #
848
+ # @example Capture and inspect output
849
+ # status, out, err = ui.run 'ls', '-la'
850
+ # ui.puts "exit #{status.exitstatus}"
851
+ #
852
+ # @example Limit displayed lines and pipe input
853
+ # File.open('data.txt') { |f| ui.run 'wc', '-l', input: f, max_lines: 5 }
854
+ #
855
+ # @overload run(*cmd, env = {}, shell: false, input: nil, max_lines: 10, **spawn_options)
856
+ # @param cmd (see #sh)
857
+ # @param max_lines [#to_int] maximum number of output lines shown at once
858
+ # @param env (see #sh)
859
+ # @param shell (see #sh)
860
+ # @param input (see #sh)
861
+ # @param spawn_options (see #sh)
862
+ # @return [Array(Process::Status, Array<String>, Array<String>)]
863
+ # three-element array of exit status, stdout lines, and stderr lines
864
+ # @return [nil] when the command could not be started
865
+ def run(*, max_lines: 10, **)
866
+ (Terminal.ansi? ? ShellRunner : DumbShellRunner).render(
867
+ self,
868
+ max_lines,
869
+ *,
870
+ **
871
+ )
1063
872
  end
1064
873
 
1065
874
  #
1066
875
  # @!endgroup
1067
876
  #
1068
877
 
1069
- private
878
+ # @private
879
+ def columns = Terminal.columns
1070
880
 
1071
- def __with(...) = NattyUI.__send__(:_with, ...)
881
+ private
1072
882
 
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)
883
+ def __determine_max_width(value)
884
+ return columns unless value
885
+ return 0 if value == 0
886
+ return value.to_int + columns if value < 0
887
+ return (value * columns).round if value < 1
888
+ [value.to_int, columns].min
1080
889
  end
1081
890
 
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
891
+ def __with(element)
892
+ NattyUI.__send__(:_begin, element)
893
+ return element unless block_given?
894
+ begin
895
+ yield(element)
896
+ ensure
897
+ NattyUI.__send__(:_end, element)
1092
898
  end
1093
899
  end
1094
900
  end
1095
901
 
1096
902
  dir = __dir__
1097
903
 
1098
- autoload :Framed, "#{dir}/framed.rb"
904
+ # @comment Elements:
905
+ autoload :Frame, "#{dir}/frame.rb"
906
+ autoload :Margin, "#{dir}/margin.rb"
907
+ autoload :Progress, "#{dir}/progress.rb"
908
+ autoload :DumbProgress, "#{dir}/dumb_progress.rb"
1099
909
  autoload :Section, "#{dir}/section.rb"
1100
- autoload :Table, "#{dir}/table.rb"
1101
910
  autoload :Task, "#{dir}/task.rb"
1102
911
  autoload :Temporary, "#{dir}/temporary.rb"
1103
- autoload :Theme, "#{dir}/theme.rb"
1104
- autoload :Utils, "#{dir}/utils.rb"
1105
- private_constant(:Framed, :Utils)
1106
912
 
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"
913
+ # @comment Helper:
914
+ autoload :Table, "#{dir}/helper/table.rb"
915
+
916
+ # @comment Utils:
917
+ autoload :Utils, "#{dir}/utils/utils.rb"
918
+ autoload :StrConst, "#{dir}/utils/str_const.rb"
919
+ private_constant(:Utils, :StrConst)
920
+
921
+ # @comment Renderer:
922
+ autoload :VBars, "#{dir}/renderer/bars.rb"
923
+ autoload :HBars, "#{dir}/renderer/bars.rb"
924
+ autoload :Choice, "#{dir}/renderer/choice.rb"
925
+ autoload :DumbChoice, "#{dir}/renderer/dumb_choice.rb"
926
+ autoload :Heading, "#{dir}/renderer/heading.rb"
927
+ autoload :HorizontalRule, "#{dir}/renderer/horizontal_rule.rb"
928
+ autoload :LS, "#{dir}/renderer/ls.rb"
929
+ autoload :CompactLS, "#{dir}/renderer/ls.rb"
930
+ autoload :Mark, "#{dir}/renderer/mark.rb"
931
+ autoload :Quote, "#{dir}/renderer/quote.rb"
932
+ autoload :Select, "#{dir}/renderer/select.rb"
933
+ autoload :DumbSelect, "#{dir}/renderer/dumb_select.rb"
934
+ autoload :Shell, "#{dir}/renderer/shell.rb"
935
+ autoload :ShellRunner, "#{dir}/renderer/shell_runner.rb"
936
+ autoload :DumbShellRunner, "#{dir}/renderer/dumb_shell_runner.rb"
937
+ autoload :TableRenderer, "#{dir}/renderer/table_renderer.rb"
1113
938
  private_constant(
939
+ :VBars,
940
+ :HBars,
1114
941
  :Choice,
1115
942
  :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
943
+ :Heading,
944
+ :HorizontalRule,
945
+ :LS,
946
+ :CompactLS,
947
+ :Mark,
948
+ :Quote,
949
+ :Select,
950
+ :DumbSelect,
951
+ :Shell,
952
+ :ShellRunner,
953
+ :DumbShellRunner,
954
+ :TableRenderer
1133
955
  )
1134
956
  end