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
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'element'
4
+
5
+ module NattyUI
6
+ # An {Element} that adds horizontal indentation and vertical spacing.
7
+ #
8
+ # Instances are created by {#margin}.
9
+ #
10
+ # All {Features} methods are available on this element.
11
+ #
12
+ # @example Horizontal indent via block
13
+ # ui.margin 0, 4 do
14
+ # ui.puts 'This line is indented by 4 characters on each side.'
15
+ # end
16
+ #
17
+ # @example Manual close with left spacing
18
+ # m = ui.margin left: 2
19
+ # ui.puts 'Indented content.'
20
+ # m.end
21
+ class Margin < Element
22
+ # @private
23
+ def puts(*, **options)
24
+ return self if @done != 0
25
+ options[:width] = @parent.columns - @prefix.width - @suffix.width
26
+ super
27
+ end
28
+
29
+ private
30
+
31
+ def done
32
+ @parent.space(@bottom) if (@done += 1) == 1
33
+ end
34
+
35
+ def initialize(parent, *args)
36
+ super(parent)
37
+
38
+ top, right, @bottom, left =
39
+ case args.size
40
+ when 0
41
+ [0, 1, 0, 1]
42
+ when 1
43
+ if (arg = args[0]).is_a?(Hash)
44
+ [
45
+ arg.fetch(:top, 0),
46
+ arg.fetch(:right, 0),
47
+ arg.fetch(:bottom, 0),
48
+ arg.fetch(:left, 0)
49
+ ]
50
+ else
51
+ Array.new(4, arg)
52
+ end
53
+ when 2
54
+ args * 2
55
+ when 3
56
+ args << args[1]
57
+ when 4
58
+ args
59
+ else
60
+ raise(
61
+ ArgumentError,
62
+ "wrong number of arguments (given #{args.size}, expected 0..4)"
63
+ )
64
+ end
65
+
66
+ top = [0, Float === top ? top.round : top.to_int].max
67
+ @bottom = [0, Float === @bottom ? @bottom.round : @bottom.to_int].max
68
+
69
+ width = parent.columns
70
+ if width < 2
71
+ @prefix = @suffix = StrConst.empty
72
+ return parent.space(top)
73
+ end
74
+
75
+ right = (width * right).round if Float === right
76
+ left = (width * left).round if Float === left
77
+
78
+ parent.space(top)
79
+ @prefix = StrConst.spacer(left.to_int.clamp(0, width))
80
+ @suffix = StrConst.spacer(right.to_int.clamp(0, width))
81
+ end
82
+ end
83
+ end
@@ -1,185 +1,149 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'element'
3
+ require_relative 'temporary'
4
4
 
5
5
  module NattyUI
6
- # @todo This chapter needs more documentation.
6
+ # A {Temporary} element that displays an progress indicator.
7
7
  #
8
- # Progress indictaor helper used by {Features.progress}.
8
+ # Instances are created by {#progress}. When a `max` value is set the
9
+ # progress is displayed as a percentage bar that fills as {#value} increases.
10
+ # Without a `max`, an open-ended dot animation grows.
9
11
  #
10
- module ProgressHelper
11
- include WithStatus
12
-
13
- # @return [String]
14
- attr_reader :title
15
-
16
- # @attribute [w] title
17
- def title=(value)
18
- @title = value
19
- redraw
20
- end
12
+ # All {Features} methods are available on this element, making it possible to
13
+ # nest output or sub-tasks inside a progress block.
14
+ #
15
+ # @example Bounded progress (percentage bar)
16
+ # ui.progress 'Loading files', max: files.size do |bar|
17
+ # files.each { load(it); bar.step }
18
+ # end
19
+ #
20
+ # @example Open-ended progress (dot animation)
21
+ # ui.progress 'Connecting…' do
22
+ # loop { ui.step; break if connected? }
23
+ # end
24
+ class Progress < Temporary
25
+ # Maximum value set at creation time.
26
+ #
27
+ # @return [Numeric] the maximum value
28
+ # @return [nil] when no maximum was set (open-ended mode)
29
+ attr_reader :max
21
30
 
22
- # @return [Float]
31
+ # Current progress value.
32
+ #
33
+ # When {#max} is set the value is clamped to `0..max`. When there is no
34
+ # maximum the value is clamped to a minimum of `0`. Setting the same
35
+ # value twice is a no-op.
36
+ #
37
+ # @example
38
+ # bar.value += 42
39
+ #
40
+ # @return [Numeric] current value
23
41
  attr_reader :value
24
42
 
25
43
  # @attribute [w] value
26
44
  def value=(val)
27
- @value = val.to_f
28
- @value = 0.0 if @value < 0
29
- @max = @value if @max&.<(@value)
30
- redraw
45
+ return if @done != 0
46
+ val = @max ? val.clamp(0, @max) : [0, val].max
47
+ return if @value == val
48
+ @value = val
49
+ draw_bar
31
50
  end
32
51
 
33
- # @return [Float]
34
- attr_reader :max
35
-
36
- # @attribute [w] max
37
- def max=(val)
38
- @max = val.to_f
39
- if @max <= 0
40
- @max = nil
41
- elsif @max < @value
42
- @max = @value
43
- end
44
- redraw
45
- end
46
-
47
- # Increment {value} and/or change {title}.
52
+ # Updates the progress title and/or value.
53
+ #
54
+ # Any number of the positional `title` arguments and the `value:` keyword
55
+ # may be omitted independently.
56
+ #
57
+ # @example Update title only
58
+ # bar.update 'Finalising…'
59
+ #
60
+ # @example Update value only
61
+ # bar.update value: 50
62
+ #
63
+ # @example Update both
64
+ # bar.update 'Step 2 of 3', value: 66
48
65
  #
49
- # @param count [Integer] increment
50
- # @param title [#to_s] new title
51
- # @return [ProgressHelper] itself
52
- def step(count: 1, title: nil)
53
- @title = title if title
54
- self.value += count
66
+ # @param title [#to_s, ...] new title text
67
+ # @param value [Numeric] new progress value
68
+ # @param popts [Hash] title print options, see {Features#puts}
69
+ # @return [Progress] itself
70
+ def update(*title, value: nil, **popts)
71
+ return if @done != 0
72
+ draw_title(*title, **popts) unless title.empty?
73
+ self.value = value if value
55
74
  self
56
75
  end
57
76
 
58
- # @private
59
- def end = NattyUI.__send__(:_end, self)
60
-
61
- alias _to_s to_s
62
- private :_to_s
63
-
64
- # @private
65
- def to_s
66
- return "#{title}: #{format('%5.2f', @value)}" unless @max
67
- "#{@title}: #{
68
- format(
69
- '%5.2f of %5.2f - %5.2f%%',
70
- @value,
71
- @max,
72
- 100.0 * (@value / @max)
73
- )
74
- }"
77
+ # Increments the progress value and optionally updates the title.
78
+ #
79
+ # @example Increment by 1
80
+ # bar.step
81
+ #
82
+ # @example Increment by a custom amount
83
+ # bar.step count: 5
84
+ #
85
+ # @example Increment and update the title
86
+ # bar.step 'Processing item 3', count: 1.5
87
+ #
88
+ # @param title (see #update)
89
+ # @param count [Numeric] amount to add
90
+ # @param popts (see #update)
91
+ # @return (see #update)
92
+ def step(*title, count: 1, **popts)
93
+ update(*title, value: @value + count, **popts)
75
94
  end
76
95
 
77
- # @private
78
- def inspect = "#{_to_s.chop} #{self}>"
79
- end
80
-
81
- class Progress
82
- include ProgressHelper
83
-
84
96
  private
85
97
 
86
- def _done(text)
87
- NattyUI.back_to_line(@pin_line)
88
- cm = Theme.current.mark(:checkmark)
89
- @parent.puts(
90
- *text,
91
- pin: @pin,
92
- first_line_prefix: cm,
93
- first_line_prefix_width: cm.width
94
- )
95
- end
96
-
97
- def _failed
98
- NattyUI.back_to_line(NattyUI.lines_written - 1) if @last&.size == 2
99
- end
100
-
101
- def initialize(parent, title, max, pin)
102
- @parent = parent
103
- @value = 0
104
- @title = title
105
- @pin = pin
106
- @pin_line = NattyUI.lines_written
107
- @style = Theme.current.task_style
108
- cm = Theme.current.mark(:current)
109
- @redraw_opts = {
110
- first_line_prefix: "#{cm}#{@style}",
111
- first_line_prefix_width: cm.width
112
- }
113
- max ? self.max = max : redraw
114
- end
115
-
116
- def redraw
117
- return if @status
118
- bar = @max ? bar(@value / @max) : moving_bar
119
- curr = bar ? [@title, bar] : [@title]
120
- return if @last == curr
121
- @pin_line = NattyUI.back_to_line(@pin_line) if @last
122
- @parent.puts(*curr, **@redraw_opts)
123
- @last = curr
124
- end
125
-
126
- def moving_bar
127
- "#{@style}#{'•' * @value}" if @value >= 1
98
+ def draw_bar
99
+ @bar_line = NattyUI.back_to_line(@bar_line)
100
+ if @max
101
+ @parent.puts(bar(@value / @max.to_f), prefix: @prefix, align: :right)
102
+ else
103
+ @parent.puts(simple, prefix: @prefix)
104
+ end
105
+ @pins&.each do |objects, opts|
106
+ @parent.puts(*objects, **opts, prefix: @prefix)
107
+ end
128
108
  end
129
109
 
130
110
  def bar(diff)
131
- size = [@parent.columns, 72].min - 11
132
- percent = format('%5.2f', 100.0 * diff)
133
- return percent if size < 10
134
- fill = '█' * (size * diff)
135
- "#{percent}% [bright_black]┃#{@style}#{fill}[bright_black]#{
136
- '░' * (size - fill.size)
137
- }┃[/]"
138
- end
139
- end
140
-
141
- class DumbProgress
142
- include ProgressHelper
143
-
144
- private
145
-
146
- def _done(text)
147
- cm = Theme.current.mark(:checkmark)
148
- @parent.puts(
149
- *text,
150
- pin: @pin,
151
- first_line_prefix: cm,
152
- first_line_prefix_width: cm.width
153
- )
111
+ size = @parent.columns - 8
112
+ size -= @prefix.width if @prefix
113
+ return diff == 1 ? '100.0%' : format('%.2f%%', 100 * diff) if size < 10
114
+
115
+ if diff == 1
116
+ "[bright_green]100.0%[bright_black]▕[bright_green on_bright_black]#{
117
+ '█' * (size * diff)
118
+ }[/bg bright_black]▏"
119
+ else
120
+ "[bright_green]#{
121
+ format('%.2f%%', 100 * diff)
122
+ }[bright_black]▕[green on_bright_black]#{
123
+ fill = '█' * (size * diff)
124
+ }[/bg bright_black]#{'░' * (size - fill.size)}▏"
125
+ end
154
126
  end
155
127
 
156
- def _failed
157
- # nop
128
+ def simple
129
+ v = Float === @value ? @value.round(2) : @value
130
+ "[bright_green]#{'•' * @value}▏[/fg]#{v}"
158
131
  end
159
132
 
160
- def initialize(parent, title, max)
161
- @parent = parent
162
- @value = @last_value = 0.0
163
- @title = title
164
- max ? self.max = max : redraw
133
+ def draw_title(*, **options)
134
+ @start_line = NattyUI.back_to_line(@start_line)
135
+ options[:cprefix] = Utils.mark[:active]
136
+ options[:prefix] = @prefix = Utils.mark[:none]
137
+ @parent.puts(*, **options)
138
+ @bar_line = NattyUI.lines_written
165
139
  end
166
140
 
167
- def redraw
168
- return if @status
169
- if @last_title != @title
170
- @parent.puts(
171
- @last_title = @title,
172
- first_line_prefix: '➔ ',
173
- first_line_prefix_width: 2
174
- )
175
- end
176
- return if @max.nil? || @value < 1
177
- percent = format('%3.0f %%', 100.0 * (@value / @max))
178
- return if @last_percent == percent
179
- @parent.puts(percent, first_line_prefix: '  ', first_line_prefix_width: 2)
180
- @last_percent = percent
141
+ def initialize(parent, max, *title, **)
142
+ super(parent)
143
+ @value = 0
144
+ @max = [0, max].max if max
145
+ draw_title(*title, **) unless title.empty?
146
+ draw_bar
181
147
  end
182
148
  end
183
-
184
- private_constant :Progress, :DumbProgress
185
149
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ # @private
5
+ class VBars
6
+ def initialize(values, min, max, normalize)
7
+ @integers = values.all?(Integer)
8
+ @values = values.map(&:to_f)
9
+ @min = min
10
+ @max = max
11
+ @normalize = normalize
12
+ end
13
+
14
+ def empty? = @values.empty?
15
+ def valid? = @values.all?(&:positive?)
16
+
17
+ def lines(columns, height, bar_width, style)
18
+ return nil if (height = height.to_int) < 3
19
+ width = columns - 1
20
+ values = adjusted(height)
21
+ rbw = (width - values.size) / values.size
22
+ rbw = bar_width.to_int.clamp(0, rbw) if rbw > 0 && bar_width != :auto
23
+ style, elyts = Utils.affix(style)
24
+ if rbw < 2
25
+ values = values.take(width) if values.size > width
26
+ db = '▉'
27
+ ds = ' '
28
+ el = values.size
29
+ else
30
+ db = "#{'█' * rbw} "
31
+ ds = ' ' * (rbw += 1)
32
+ el = (rbw * values.size) - 1
33
+ end
34
+ (height - 1)
35
+ .downto(1)
36
+ .map do |i|
37
+ "#{style}#{values.map { it < i ? ds : db }.join}#{elyts}"
38
+ end << "#{style}#{'▔' * el}#{elyts}"
39
+ end
40
+
41
+ protected
42
+
43
+ def adjusted(size)
44
+ return normalized(size) if @normalize
45
+ max = @values.max
46
+ max = @max.to_f if @max&.>(max)
47
+ return Array.new(@values.size, size) if max <= 0
48
+ @values.map { ((it / max) * size).round }
49
+ end
50
+
51
+ private
52
+
53
+ def normalized(size)
54
+ min, max = @values.minmax
55
+ min = @min.to_f if @min&.<(min)
56
+ max = @max.to_f if @max&.>(max)
57
+ max -= min
58
+ return Array.new(@values.size, size) if max <= 0
59
+ @values.map { (((it - min) / max) * size).round }
60
+ end
61
+ end
62
+
63
+ # @private
64
+ class HBars < VBars
65
+ def with_text(style)
66
+ @texts =
67
+ if @integers
68
+ @values.map { StrConst[it] }
69
+ else
70
+ @values.map { StrConst[it.round(2)] }
71
+ end
72
+ @texts_width = @texts.max_by(&:width).width
73
+ style, elyts = Utils.affix(style)
74
+ @texts.map! { "#{style}#{it.rjust(@texts_width)}#{elyts}" }
75
+ @texts_width += 1
76
+ self
77
+ end
78
+
79
+ def lines(columns, width, style)
80
+ width = Utils.as_width(width, columns)
81
+ return if width < 2
82
+ if @texts_width
83
+ w = width - @texts_width
84
+ w < 2 ? @texts = nil : width = w
85
+ end
86
+ style, elyts = Utils.affix(style)
87
+ lines = adjusted(width).map! { "#{style}▕#{'▆' * it}#{elyts}" }
88
+ @texts ? @texts.zip(lines).map!(&:join) : lines
89
+ end
90
+ end
91
+
92
+ private_constant :VBars, :HBars
93
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ # @private
5
+ class Choice
6
+ def select(abortable, selected)
7
+ start_line = pin_line = NattyUI.lines_written
8
+ draw(last = current = @results.index(selected) || 0)
9
+ Terminal.on_key_event do |event|
10
+ case event.name
11
+ when 'Esc', 'Ctrl+c'
12
+ break if abortable
13
+ when 'Enter', 'Space'
14
+ break @results[current]
15
+ when 'Home'
16
+ current = 0
17
+ when 'End'
18
+ current = @results.size - 1
19
+ when 'Up', 'Back', 'Shift+Tab', 'i', 'w'
20
+ current = @results.size - 1 if (current -= 1) < 0
21
+ when 'Down', 'Tab', 'k', 's'
22
+ current = 0 if (current += 1) == @results.size
23
+ else
24
+ next unless event.simple?
25
+ c = event.key.ord
26
+ current = (c - 48).clamp(0, @results.size - 1) if c.between?(48, 57)
27
+ end
28
+ next if last == current
29
+ pin_line = NattyUI.back_to_line(pin_line, erase: false)
30
+ draw(last = current)
31
+ end
32
+ ensure
33
+ NattyUI.back_to_line(start_line)
34
+ end
35
+
36
+ private
37
+
38
+ def draw(current)
39
+ @texts.each_with_index do |str, idx|
40
+ @parent.puts(
41
+ str,
42
+ cprefix: Utils.mark[idx == current ? :current_item : :item],
43
+ prefix: Utils.mark[:none]
44
+ )
45
+ end
46
+ end
47
+
48
+ def initialize(parent, texts, results)
49
+ @parent = parent
50
+ @texts = texts
51
+ @results = results
52
+ end
53
+ end
54
+
55
+ private_constant :Choice
56
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ # @private
5
+ class DumbChoice
6
+ def select(abortable, _selected)
7
+ Terminal.on_key_event do |event|
8
+ break if abortable && %w[Esc Ctrl+c].include?(event.name)
9
+ next unless event.simple?
10
+ code = event.raw.upcase
11
+ if @results.size <= 9
12
+ next unless ('1'..'9').include?(code)
13
+ code = @results[code.ord - 49] and return code
14
+ elsif ('A'..'Z').include?(code)
15
+ code = @results[code.ord - 65] and return code
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def initialize(parent, texts, results)
23
+ @results = results
24
+ glyph = results.size <= 9 ? 1 : 'A'
25
+ prefix = StrConst.spacer(4)
26
+ texts.each do |str|
27
+ parent.puts(str, prefix:, cprefix: StrConst.new("[#{glyph}] ", 4))
28
+ glyph = glyph.succ
29
+ end
30
+ end
31
+ end
32
+
33
+ private_constant :DumbChoice
34
+ end
@@ -0,0 +1,60 @@
1
+ module NattyUI
2
+ # @private
3
+ class DumbSelect
4
+ def select(abortable)
5
+ draw
6
+ Terminal.on_key_event do |event|
7
+ if abortable && %w[Esc Ctrl+c].include?(event.name)
8
+ @parent.space
9
+ break []
10
+ end
11
+ if event.name == 'Enter'
12
+ @parent.space
13
+ break @items.filter_map { |ret, _str, selected| ret if selected }
14
+ end
15
+ next unless event.simple?
16
+ code = event.raw.upcase
17
+ if @items.size <= 9
18
+ next unless ('1'..'9').include?(code)
19
+ current = code.ord - 49
20
+ elsif ('A'..'Z').include?(code)
21
+ current = code.ord - 65
22
+ else
23
+ next
24
+ end
25
+ next if current >= @items.size
26
+ *last, selected = @items[current]
27
+ @items[current] = last << !selected
28
+ @parent.space
29
+ draw
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def draw
36
+ @parent.ls(
37
+ *@items.map do |_ret, str, selected|
38
+ "[\\#{selected ? 'X' : ' '}] #{str}"
39
+ end,
40
+ glyph: @items.size <= 9 ? 1 : 'A'
41
+ )
42
+ # # glyph = @items.size <= 9 ? 1 : 'A'
43
+ # # @items.each do |_ret, str, selected|
44
+ # # @parent.puts(
45
+ # # str,
46
+ # # cprefix: true,
47
+ # # prefix: "#{glyph}: [\\#{selected ? 'X' : ' '}] "
48
+ # # )
49
+ # # glyph = glyph.succ
50
+ # # end
51
+ end
52
+
53
+ def initialize(parent, items)
54
+ @parent = parent
55
+ @items = items
56
+ end
57
+ end
58
+
59
+ private_constant :DumbSelect
60
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ # @private
5
+ module DumbShellRunner
6
+ def self.render(parent, _tail, *, **)
7
+ out, err = [], []
8
+ width = parent.columns
9
+ ret =
10
+ Terminal.sh(*, **) do |line, kind|
11
+ (kind == :error ? err : out) << line
12
+ parent.puts(line, width:, spaces: true, prefix: Utils.mark[kind])
13
+ end
14
+ [ret, out, err] if ret
15
+ end
16
+ end
17
+
18
+ private_constant :DumbShellRunner
19
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ # @private
5
+ class Heading
6
+ def self.render(parent, level, title)
7
+ prefix, suffix = @defined[level.to_int.clamp(1, 6) - 1]
8
+ parent.puts(title, prefix: prefix, suffix: suffix, eol: false)
9
+ end
10
+
11
+ attr_reader :to_ary
12
+
13
+ def initialize(str, style)
14
+ @to_ary = [
15
+ StrConst["#{style}#{str}#{Ansi::RESET} "],
16
+ StrConst[" #{style}#{str.reverse}#{Ansi::RESET}"]
17
+ ]
18
+ end
19
+
20
+ style = Ansi[:bright_blue]
21
+ @defined =
22
+ %w[╸╸╺╸╺━━━ ╴╶╴╶─═══ ╴╶╴╶─── ════ ━━━━ ────].map! { new(it, style) }
23
+ end
24
+
25
+ private_constant :Heading
26
+ end