natty-ui 0.34.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +0 -1
  3. data/README.md +6 -6
  4. data/examples/24bit-colors.rb +9 -5
  5. data/examples/3bit-colors.rb +7 -7
  6. data/examples/8bit-colors.rb +5 -5
  7. data/examples/attributes.rb +2 -3
  8. data/examples/elements.rb +9 -6
  9. data/examples/examples.rb +9 -9
  10. data/examples/frames.rb +31 -0
  11. data/examples/hbars.rb +6 -3
  12. data/examples/info.rb +13 -10
  13. data/examples/key-codes.rb +8 -9
  14. data/examples/ls.rb +24 -22
  15. data/examples/named-colors.rb +4 -3
  16. data/examples/sections.rb +27 -17
  17. data/examples/select.rb +28 -0
  18. data/examples/sh.rb +25 -7
  19. data/examples/tables.rb +19 -37
  20. data/examples/tasks.rb +32 -22
  21. data/examples/vbars.rb +5 -3
  22. data/lib/natty-ui/dumb_progress.rb +68 -0
  23. data/lib/natty-ui/element.rb +64 -65
  24. data/lib/natty-ui/features.rb +773 -872
  25. data/lib/natty-ui/frame.rb +87 -0
  26. data/lib/natty-ui/helper/table.rb +1376 -0
  27. data/lib/natty-ui/margin.rb +83 -0
  28. data/lib/natty-ui/progress.rb +116 -149
  29. data/lib/natty-ui/renderer/bars.rb +93 -0
  30. data/lib/natty-ui/renderer/choice.rb +56 -0
  31. data/lib/natty-ui/renderer/dumb_choice.rb +34 -0
  32. data/lib/natty-ui/renderer/dumb_select.rb +60 -0
  33. data/lib/natty-ui/renderer/dumb_shell_runner.rb +19 -0
  34. data/lib/natty-ui/renderer/heading.rb +26 -0
  35. data/lib/natty-ui/renderer/horizontal_rule.rb +32 -0
  36. data/lib/natty-ui/{ls_renderer.rb → renderer/ls.rb} +15 -27
  37. data/lib/natty-ui/renderer/mark.rb +13 -0
  38. data/lib/natty-ui/renderer/quote.rb +13 -0
  39. data/lib/natty-ui/renderer/select.rb +63 -0
  40. data/lib/natty-ui/renderer/shell.rb +15 -0
  41. data/lib/natty-ui/renderer/shell_runner.rb +29 -0
  42. data/lib/natty-ui/renderer/table_renderer.rb +429 -0
  43. data/lib/natty-ui/section.rb +142 -41
  44. data/lib/natty-ui/task.rb +39 -27
  45. data/lib/natty-ui/temporary.rb +27 -14
  46. data/lib/natty-ui/utils/border.rb +139 -0
  47. data/lib/natty-ui/utils/str_const.rb +62 -0
  48. data/lib/natty-ui/utils/utils.rb +47 -0
  49. data/lib/natty-ui/version.rb +1 -1
  50. data/lib/natty-ui.rb +87 -30
  51. metadata +31 -28
  52. data/examples/cols.rb +0 -38
  53. data/examples/illustration.rb +0 -60
  54. data/examples/options.rb +0 -28
  55. data/examples/themes.rb +0 -51
  56. data/lib/natty-ui/attributes.rb +0 -593
  57. data/lib/natty-ui/choice.rb +0 -67
  58. data/lib/natty-ui/dumb_choice.rb +0 -47
  59. data/lib/natty-ui/dumb_options.rb +0 -64
  60. data/lib/natty-ui/framed.rb +0 -51
  61. data/lib/natty-ui/hbars_renderer.rb +0 -66
  62. data/lib/natty-ui/options.rb +0 -78
  63. data/lib/natty-ui/shell_renderer.rb +0 -91
  64. data/lib/natty-ui/table.rb +0 -325
  65. data/lib/natty-ui/table_renderer.rb +0 -165
  66. data/lib/natty-ui/theme.rb +0 -403
  67. data/lib/natty-ui/utils.rb +0 -111
  68. data/lib/natty-ui/vbars_renderer.rb +0 -49
  69. data/lib/natty-ui/width_finder.rb +0 -137
  70. data/natty-ui.gemspec +0 -34
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'element'
4
-
5
- module NattyUI
6
- class DumbOptions < Element
7
- def select
8
- yield(self) if block_given?
9
- draw
10
- Terminal.on_key_event do |event|
11
- return if @abortable && %w[Esc Ctrl+c].include?(event.name)
12
- return @opts.transform_values(&:last) if event.name == 'Enter'
13
- next true unless event.simple?
14
- code = event.raw.upcase
15
- if @opts.size <= 9
16
- next true unless ('1'..'9').include?(code)
17
- offset = 49
18
- elsif ('A'..'Z').include?(code)
19
- offset = 65
20
- else
21
- next true
22
- end
23
- key = @opts.keys[code.ord - offset] or next true
24
- @opts[key][-1] = !@opts[key][-1]
25
- @parent.space
26
- draw
27
- end
28
- ensure
29
- @parent.space
30
- end
31
-
32
- private
33
-
34
- def initialize(parent, opts, abortable, selected)
35
- super(parent)
36
- @opts =
37
- opts.to_h do |k, v|
38
- v.is_a?(Enumerable) ? [k, [v[0], !!v[-1]]] : [k, [k, !!v]]
39
- end
40
- @abortable = abortable
41
- @current = @opts.key?(selected) ? selected : @opts.first.first
42
- theme = Theme.current
43
- @marks = {
44
- true => theme.mark(:checkmark),
45
- false => theme.mark(:choice)
46
- }.compare_by_identity
47
- end
48
-
49
- def draw
50
- glyph = @opts.size <= 9 ? 1 : 'A'
51
- @opts.each_pair do |_, (str, selected)|
52
- mark = @marks[selected]
53
- @parent.puts(
54
- str,
55
- first_line_prefix: "[\\#{glyph}] #{mark}",
56
- first_line_prefix_width: mark.width + 2
57
- )
58
- glyph = glyph.succ
59
- end
60
- end
61
- end
62
-
63
- private_constant :DumbOptions
64
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'element'
4
-
5
- module NattyUI
6
- # {Element} with a frame around the content used by {Features.framed}.
7
- #
8
- class Framed < Element
9
- # @private
10
- def closed? = @bottom ? false : true
11
-
12
- # @private
13
- def puts(*objects, **options)
14
- return self if closed?
15
- options[:align] = @align
16
- options[:expand] = true
17
- super
18
- end
19
-
20
- # @private
21
- def done
22
- return if closed?
23
- @parent.puts(@bottom)
24
- @bottom = nil
25
- end
26
-
27
- # @private
28
- def inspect = "#{_to_s.chop} align=#{@align.inspect} closed?=#{closed?}>"
29
-
30
- private
31
-
32
- def initialize(parent, align, chars, style, msg)
33
- super(parent)
34
- @align = align
35
- if style
36
- style = Ansi[*style]
37
- @prefix = "#{style}#{chars[9]}[/] "
38
- @suffix = " #{style}#{chars[9]}[/]"
39
- else
40
- @prefix = "#{chars[9]} "
41
- @suffix = " #{chars[9]}"
42
- end
43
- @prefix_width = 2
44
- @suffix_width = 2
45
- line = chars[10] * (parent.columns - 2)
46
- parent.puts("#{style}#{chars[0]}#{line}#{chars[2]}")
47
- @bottom = "#{style}#{chars[6]}#{line}#{chars[8]}"
48
- puts(*msg) unless msg.empty?
49
- end
50
- end
51
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'utils'
4
-
5
- module NattyUI
6
- class HBarsRenderer
7
- def initialize(values, min, max)
8
- @values = values
9
- @min = min
10
- @max = max
11
- end
12
-
13
- def with_text(style)
14
- @texts = @values.map { Str.new(_1) }
15
- @texts_width = @texts.max_by(&:width).width
16
- @texts.map!(&format_text(style, @texts_width))
17
- self
18
- end
19
-
20
- def lines(width, style, normalize)
21
- if @texts
22
- width -= @texts_width
23
- return @texts if width < 3
24
- end
25
- width -= 1
26
- fmt = format(style)
27
- vals = normalize ? normalized(width, &fmt) : adjusted(width, &fmt)
28
- vals = @texts.zip(vals).map!(&:join) if @texts
29
- vals
30
- end
31
-
32
- private
33
-
34
- def format_text(style, width)
35
- return ->(l) { "#{' ' * (width - l.width)}#{l}" } unless style
36
- bot = Ansi[*style]
37
- eot = Ansi::RESET
38
- ->(l) { "#{bot}#{' ' * (width - l.width)}#{l}#{eot}" }
39
- end
40
-
41
- def format(style)
42
- return ->(l) { "▕#{'▆' * l}" } unless style
43
- bos = Ansi[*style]
44
- eos = Ansi::RESET
45
- ->(l) { "#{bos}▕#{'▆' * l}#{eos}" }
46
- end
47
-
48
- def adjusted(size)
49
- vals = @values.map(&:to_f)
50
- max = vals.max
51
- max = @max.to_f if @max&.>(max)
52
- vals.map! { yield(((_1 / max) * size).round) }
53
- end
54
-
55
- def normalized(size)
56
- vals = @values.map(&:to_f)
57
- min, max = vals.minmax
58
- min = @min.to_f if @min&.<(min)
59
- max = @max.to_f if @max&.>(max)
60
- max -= min
61
- vals.map! { yield((((_1 - min) / max) * size).round) }
62
- end
63
- end
64
-
65
- private_constant :HBarsRenderer
66
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'element'
4
-
5
- module NattyUI
6
- class Options < Element
7
- def select
8
- yield(self) if block_given?
9
- pin_line = NattyUI.lines_written
10
- draw
11
- last = @current
12
- Terminal.on_key_event do |event|
13
- case event.name
14
- when 'Esc', 'Ctrl+c'
15
- return if @abortable
16
- when 'Enter'
17
- return @opts.transform_values(&:last)
18
- when 'Space'
19
- (last = @opts[@current])[-1] = !last[-1]
20
- when 'a'
21
- @opts.transform_values { _1[-1] = true }
22
- last = nil
23
- when 'n'
24
- @opts.transform_values { _1[-1] = false }
25
- last = nil
26
- when 'Home'
27
- @current = @opts.first.first
28
- when 'End'
29
- @current = @opts.keys.last
30
- when 'Up', 'Back', 'Shift+Tab', 'i'
31
- keys = @opts.keys
32
- @current = keys[keys.index(@current) - 1]
33
- when 'Down', 'Tab', 'k'
34
- keys = @opts.keys
35
- @current = keys[keys.index(@current) + 1] || keys[0]
36
- end
37
- next true if last == @current
38
- pin_line = NattyUI.back_to_line(pin_line, erase: false)
39
- draw
40
- last = @current
41
- end
42
- ensure
43
- NattyUI.back_to_line(@start_line)
44
- end
45
-
46
- private
47
-
48
- def initialize(parent, opts, abortable, selected)
49
- super(parent)
50
- @opts =
51
- opts.to_h do |k, v|
52
- v.is_a?(Enumerable) ? [k, [v[0], !!v[-1]]] : [k, [k, !!v]]
53
- end
54
- @abortable = abortable
55
- @current = @opts.key?(selected) ? selected : @opts.first.first
56
- @start_line = NattyUI.lines_written
57
- theme = Theme.current
58
- @style = {
59
- false => theme.choice_style,
60
- true => theme.choice_current_style
61
- }.compare_by_identity
62
- end
63
-
64
- def draw
65
- states = NattyUI::Theme.current.option_states
66
- @opts.each_pair do |key, (str, selected)|
67
- mark = states.dig(current = key == @current, selected)
68
- @parent.puts(
69
- "#{@style[current]}#{str}",
70
- first_line_prefix: mark,
71
- first_line_prefix_width: mark.width
72
- )
73
- end
74
- end
75
- end
76
-
77
- private_constant :Options
78
- end
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'theme'
4
-
5
- module NattyUI
6
- module ShellRenderer
7
- def self.sh(parent, cmd, options, space)
8
- opts_out, opts_err = opts_from(Theme.current)
9
- Terminal.sh(*cmd, **options) do |line, kind|
10
- parent.puts(
11
- space ? line.gsub("\t", '    ').gsub(/[[:space:]]/, ' ') : line,
12
- **(kind == :error ? opts_err : opts_out)
13
- )
14
- end
15
- end
16
-
17
- if Terminal.ansi?
18
- def self.opts_from(theme)
19
- out = theme.mark(:sh_out)
20
- err = theme.mark(:sh_err)
21
- [
22
- {
23
- bbcode: false,
24
- prefix: "#{out}#{theme.sh_out_style}",
25
- prefix_width: out.width
26
- },
27
- {
28
- bbcode: false,
29
- prefix: "#{err}#{theme.sh_err_style}",
30
- prefix_width: err.width
31
- }
32
- ]
33
- end
34
-
35
- def self.run(parent, cmd, options, space, tail)
36
- theme = Theme.current
37
- opref = "#{theme.mark(:sh_out)}#{theme.sh_out_style}"
38
- epref = "#{theme.mark(:sh_err)}#{theme.sh_err_style}"
39
- out, err, show = [], [], []
40
- start = NattyUI.lines_written
41
- [
42
- Terminal.sh(*cmd, **options) do |line, kind|
43
- start = NattyUI.back_to_line(start)
44
- ol = space ? line.gsub("\t", '    ').gsub(/[[:space:]]/, ' ') : line
45
- if kind == :error
46
- err << line
47
- show << "#{epref}#{ol}"
48
- else
49
- out << line
50
- show << "#{opref}#{ol}"
51
- end
52
- parent.puts(*show, bbcode: false, tail: tail)
53
- end,
54
- out,
55
- err
56
- ]
57
- end
58
- else
59
- def self.opts_from(theme)
60
- out = theme.mark(:sh_out)
61
- err = theme.mark(:sh_err)
62
- [
63
- { bbcode: false, prefix: out, prefix_width: out.width },
64
- { bbcode: false, prefix: err, prefix_width: err.width }
65
- ]
66
- end
67
-
68
- def self.run(parent, cmd, options, _space, _tail)
69
- theme = Theme.current
70
- opref, epref = theme.mark(:sh_out), theme.mark(:sh_err)
71
- out, err = [], []
72
- [
73
- Terminal.sh(*cmd, **options) do |line, kind|
74
- ol = line.gsub("\t", '    ').gsub(/[[:space:]]/, ' ')
75
- if kind == :error
76
- err << line
77
- parent.puts("#{epref}#{ol}", bbcode: false)
78
- else
79
- out << line
80
- parent.puts("#{opref}#{ol}", bbcode: false)
81
- end
82
- end,
83
- out,
84
- err
85
- ]
86
- end
87
- end
88
- end
89
-
90
- private_constant :ShellRenderer
91
- end
@@ -1,325 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'attributes'
4
- require_relative 'table_renderer'
5
-
6
- module NattyUI
7
- # @todo This chapter needs more documentation.
8
- #
9
- # Collection of rows and columns used by {Features.table}.
10
- #
11
- class Table
12
- class Column < NattyUI::Attributes
13
- include Width
14
-
15
- # @return [Integer] column index
16
- attr_reader :index
17
-
18
- def width
19
- @width ||= find_width
20
- end
21
-
22
- def to_s = "#{super.chop} @index:#{@index} @width:#{width.inspect}>"
23
- alias inspect to_s
24
-
25
- def assign(**attributes)
26
- return self if attributes.empty?
27
- @parent.each_cell_of(@index) { _1.attributes.merge!(**attributes) }
28
- self
29
- end
30
-
31
- private
32
-
33
- def find_width
34
- min = max = nil
35
- @parent.each_cell_of(@index) do |cell|
36
- next unless cell
37
- m = cell.attributes.min_width
38
- min = m if m && (min.nil? || m < min)
39
- m = cell.attributes.max_width
40
- max = m if m && (max.nil? || max < m)
41
- end
42
- wh_from(min, max)
43
- end
44
-
45
- def respond_to_missing?(name, _)
46
- return suoer unless name.end_with?('=')
47
- Cell::Attributes.public_method_defined?(name) || super
48
- end
49
-
50
- def method_missing(name, *args, **kwargs)
51
- return super unless name.end_with?('=')
52
- return super unless Cell::Attributes.public_method_defined?(name)
53
- @parent.each_cell_of(@index) { _1.attributes.__send__(name, *args) }
54
- args[0]
55
- end
56
-
57
- def initialize(parent, index, **attributes)
58
- super(**attributes)
59
- @parent = parent
60
- @index = index
61
- end
62
- end
63
-
64
- class ColumnsCollection
65
- include Enumerable
66
-
67
- # @return [Integer] count of columns
68
- def count = columns.size
69
-
70
- def empty? = columns.empty?
71
-
72
- # @return [Column, nil] column at given index
73
- def [](index) = columns[index]
74
-
75
- def each(&block) = columns.each(&block)
76
-
77
- def to_s = "#{super.chop} @columns:#{columns.inspect}>"
78
- alias inspect to_s
79
-
80
- private
81
-
82
- def columns
83
- cc = @parent.column_count
84
- case cc <=> @columns.size
85
- when -1
86
- @columns = @columns.take(cc)
87
- when 1
88
- bi = @columns.size
89
- @columns +=
90
- Array.new(cc - @columns.size) { Column.new(@parent, bi + _1) }
91
- else
92
- @columns
93
- end
94
- end
95
-
96
- def initialize(parent)
97
- @parent = parent
98
- @columns = []
99
- end
100
-
101
- def initialize_copy(*_)
102
- super
103
- @columns = []
104
- end
105
- end
106
-
107
- class Row
108
- include Enumerable
109
-
110
- def empty? = @cells.empty?
111
-
112
- # @return [Integer] count of cells
113
- def count = @cells.size
114
-
115
- def each(&block) = @cells.each(&block)
116
-
117
- # @return [Cell, nil] cell at given index
118
- def [](index) = @cells[index]
119
-
120
- # @return [Cell] created cell
121
- def []=(index, *args)
122
- @cells[index] = create_cell(args)
123
- @cells.map! { _1 || Cell.new }
124
- end
125
-
126
- # Add a new cell to the row with given `text` and `attributes`.
127
- # @return [Cell] created cell
128
- def add(*text, **attributes)
129
- nc = Cell.new(*text, **attributes)
130
- @cells << nc
131
- block_given? ? yield(nc) : nc
132
- end
133
-
134
- def delete(cell)
135
- cell.is_a?(Cell) ? @cells.delete(cell) : @cells.delete_at(cell)
136
- self
137
- end
138
-
139
- # Add a new cell to the row with given `text`.
140
- # @return [Row] itself
141
- def <<(text)
142
- add(text)
143
- self
144
- end
145
-
146
- def assign(**attributes)
147
- return self if attributes.empty?
148
- @cells.each { _1.attributes.merge!(**attributes) }
149
- self
150
- end
151
-
152
- private
153
-
154
- def respond_to_missing?(name, _)
155
- return super unless name.end_with?('=')
156
- Cell::Attributes.public_method_defined?(name) || super
157
- end
158
-
159
- def method_missing(name, *args, **_)
160
- return super unless name.end_with?('=')
161
- return super unless Cell::Attributes.public_method_defined?(name)
162
- @cells.each { _1.attributes.__send__(name, *args) }
163
- args[0]
164
- end
165
-
166
- def initialize
167
- @cells = []
168
- end
169
-
170
- def initialize_copy(*_)
171
- super
172
- @cells = @cells.map(&:dup)
173
- end
174
-
175
- def create_cell(args)
176
- args.flatten!(1)
177
- Cell.new(*args)
178
- end
179
- end
180
-
181
- class Cell
182
- include TextWithAttributes
183
-
184
- class Attributes < NattyUI::Attributes
185
- prepend Width
186
- prepend Padding
187
- prepend Align
188
- prepend Vertical
189
- prepend Style
190
-
191
- # Whether the text's line breaks are processed.
192
- #
193
- # @return [true, false]
194
- attr_reader :eol
195
-
196
- # @attribute [w] eol
197
- def eol=(value)
198
- @eol = value ? true : false
199
- end
200
-
201
- protected
202
-
203
- def _assign(opt)
204
- @eol = opt[:eol]
205
- @eol = true if @eol.nil?
206
- super
207
- end
208
-
209
- def _store(opt)
210
- opt[:eol] = false unless @eol
211
- super
212
- end
213
- end
214
- end
215
-
216
- class Attributes < NattyUI::Attributes
217
- prepend Border
218
- prepend BorderStyle
219
- prepend Position
220
-
221
- # Whether the table has a border around.
222
- #
223
- # @return [true, false]
224
- attr_reader :border_around
225
-
226
- # @attribute [w] border_around
227
- def border_around=(value)
228
- @border_around = value ? true : false
229
- end
230
-
231
- # Maximum table width.
232
- #
233
- # @return [Integer, nil]
234
- attr_reader :max_width
235
-
236
- # @attribute [w] max_width
237
- def max_width=(value)
238
- if value.is_a?(Float)
239
- return @max_width = nil if value < 0
240
- return @max_width = value if value < 1
241
- end
242
- value = value.to_i
243
- @max_width = value <= 0 ? nil : value
244
- end
245
-
246
- protected
247
-
248
- def _assign(opt)
249
- @border_around = opt[:border_around]
250
- self.max_width = opt[:max_width]
251
- super
252
- end
253
-
254
- def _store(opt)
255
- opt[:border_around] = true if @border_around
256
- opt[:max_width] = @max_width if @max_width
257
- super
258
- end
259
- end
260
-
261
- include Enumerable
262
- include WithAttributes
263
-
264
- # @return [ColumnsCollection] table columns
265
- attr_reader :columns
266
-
267
- def empty? = @rows.empty?
268
-
269
- # @return [Integer] count of rows
270
- def count = @rows.size
271
-
272
- # @return [Integer] count of columns
273
- def column_count = @rows.empty? ? 0 : @rows.max_by(&:count).count
274
-
275
- def each(&block) = @rows.each(&block)
276
-
277
- def [](row_index, column_index = nil)
278
- row = @rows[row_index] or return
279
- column_index ? row[column_index] : row
280
- end
281
-
282
- # @return [Row] created row
283
- def add(*text, **attributes)
284
- unless text[0].is_a?(Hash)
285
- @rows << (nr = Row.new)
286
- text.each { nr.add(_1, **attributes) }
287
- return block_given? ? yield(nr) : nr
288
- end
289
- new_rows = []
290
- text[0].each_pair do |key, value|
291
- new_rows << (nr = Row.new)
292
- @rows << nr
293
- nr.add(key, **attributes)
294
- nr.add(value, **attributes)
295
- yield(nr) if block_given?
296
- end
297
- new_rows
298
- end
299
-
300
- def delete(row)
301
- row.is_a?(Row) ? @rows.delete(row) : @rows.delete_at(row)
302
- end
303
-
304
- def each_cell_of(column_index)
305
- return to_enum(__method__, column_index) unless block_given?
306
- return if (column_index = column_index.to_i) >= column_count
307
- @rows.each { yield(_1[column_index]) }
308
- nil
309
- end
310
-
311
- private
312
-
313
- def initialize(**attributes)
314
- super
315
- @rows = []
316
- @columns = ColumnsCollection.new(self)
317
- end
318
-
319
- def initialize_copy(*_)
320
- super
321
- @columns = ColumnsCollection.new(self)
322
- @rows = @rows.map(&:dup)
323
- end
324
- end
325
- end