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,593 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module NattyUI
4
- # @todo This chapter needs more documentation.
5
- #
6
- class Attributes
7
- # @return [Attributes] updated copy of itself
8
- def merge(**attributes)
9
- attributes.empty? ? dup : dup._assign(attributes)
10
- end
11
-
12
- # @return [Attributes] itself
13
- def merge!(**attributes) = attributes.empty? ? self : _assign(attributes)
14
- alias assign merge!
15
-
16
- # @private
17
- def to_hash = _store({})
18
-
19
- # @private
20
- def to_h(&block) = block ? _store({}).to_h(&block) : _store({})
21
-
22
- private
23
-
24
- def initialize(**attributes)
25
- _init
26
- _assign(attributes) unless attributes.empty?
27
- end
28
-
29
- def _init = nil
30
- def _assign(_opt) = self
31
- def _store(opt) = opt
32
- def as_uint(value) = [0, value.to_i].max
33
- def as_nint(value) = ([0, value.to_i].max if value)
34
-
35
- def as_wh(value)
36
- return unless value
37
- return value > 0 ? value : nil if value.is_a?(Numeric)
38
- value.is_a?(Range) ? wh_from(value.begin, value.end) : nil
39
- end
40
-
41
- def wh_from(min, max)
42
- min = normalized(min)
43
- max = normalized(max)
44
- return max ? (..max) : nil unless min
45
- return Range.new(min, nil) unless max
46
- min == max ? min : Range.new(*[min, max].minmax)
47
- end
48
-
49
- def normalized(value)
50
- return value < 0 ? nil : value if value.is_a?(Float) && value < 1
51
- (value = value.to_i) < 1 ? nil : value
52
- end
53
-
54
- module Align
55
- # Horizontal element alignment.
56
- #
57
- # @return [:left, :right, :centered]
58
- attr_reader :align
59
-
60
- # @attribute [w] align
61
- def align=(value)
62
- @align = Utils.align(value)
63
- end
64
-
65
- protected
66
-
67
- def _init
68
- @align = :left
69
- super
70
- end
71
-
72
- def _assign(opt)
73
- self.align = opt[:align] if opt.key?(:align)
74
- super
75
- end
76
-
77
- def _store(opt)
78
- opt[:align] = @align if @align != :left
79
- super
80
- end
81
- end
82
-
83
- module Position
84
- # Horizontal element position.
85
- #
86
- # @return [nil, :right, :centered]
87
- attr_reader :position
88
-
89
- # @attribute [w] position
90
- def position=(value)
91
- @position = Utils.position(value)
92
- end
93
-
94
- protected
95
-
96
- def _assign(opt)
97
- self.position = opt[:position] if opt.key?(:position)
98
- super
99
- end
100
-
101
- def _store(opt)
102
- opt[:position] = @position if @position
103
- super
104
- end
105
- end
106
-
107
- module Vertical
108
- # Vertical element alignment.
109
- #
110
- # @return [:top, :bottom, :middle]
111
- attr_reader :vertical
112
-
113
- # @attribute [w] vertical
114
- def vertical=(value)
115
- @vertical = Utils.vertical(value)
116
- end
117
-
118
- protected
119
-
120
- def _init
121
- @vertical = :top
122
- super
123
- end
124
-
125
- def _assign(opt)
126
- self.vertical = opt[:vertical] if opt.key?(:vertical)
127
- super
128
- end
129
-
130
- def _store(opt)
131
- opt[:vertical] = @vertical if @vertical != :top
132
- super
133
- end
134
- end
135
-
136
- module Width
137
- # Element width.
138
- #
139
- # @return [Integer] dedicated element width
140
- # @return [Range] width range: {#min_width}..{#max_width}
141
- # @return [nil] when unassigned
142
- attr_reader :width
143
-
144
- # @attribute [w] width
145
- def width=(value)
146
- @width = as_wh(value)
147
- end
148
-
149
- # Minimum element width.
150
- #
151
- # @attribute [r] min_width
152
- # @return [Integer, nil]
153
- def min_width = width.is_a?(Range) ? @width.begin : @width
154
-
155
- # @attribute [w] min_width
156
- def min_width=(value)
157
- @width = wh_from(value, max_width)
158
- end
159
-
160
- # Maximum element width.
161
- #
162
- # @attribute [r] max_width
163
- # @return [Integer, nil]
164
- def max_width = width.is_a?(Range) ? @width.end : @width
165
-
166
- # @attribute [w] max_width
167
- def max_width=(value)
168
- @width = wh_from(min_width, value)
169
- end
170
-
171
- protected
172
-
173
- def _assign(opt)
174
- @width = as_wh(opt[:width]) if opt.key?(:width)
175
- self.min_width = opt[:min_width] if opt.key?(:min_width)
176
- self.max_width = opt[:max_width] if opt.key?(:max_width)
177
- super
178
- end
179
-
180
- def _store(opt)
181
- opt[:width] = @width if @width
182
- super
183
- end
184
- end
185
-
186
- module Height
187
- # Element height.
188
- #
189
- # @return [Integer] dedicated element height
190
- # @return [Range] height range: {#min_height}..{#max_height}
191
- # @return [nil] when unassigned
192
- attr_reader :height
193
-
194
- # @attribute [w] height
195
- def height=(value)
196
- @height = as_wh(value)
197
- end
198
-
199
- # Minimum element height.
200
- #
201
- # @attribute [r] min_height
202
- # @return [Integer, nil]
203
- def min_height = @height.is_a?(Range) ? @height.begin : @height
204
-
205
- # @attribute [w] min_height
206
- def min_height=(value)
207
- @height = wh_from(value.to_i, max_height)
208
- end
209
-
210
- # Maximum element height.
211
- #
212
- # @attribute [r] max_height
213
- # @return [Integer, nil]
214
- def max_height = @height.is_a?(Range) ? @height.begin : @height
215
-
216
- # @attribute [w] max_height
217
- def max_height=(value)
218
- @height = wh_from(min_height, value.to_i)
219
- end
220
-
221
- protected
222
-
223
- def _assign(opt)
224
- @height = as_wh(opt[:height]) if opt.key?(:height)
225
- self.min_height = opt[:min_height] if opt.key?(:min_height)
226
- self.max_height = opt[:max_height] if opt.key?(:max_height)
227
- super
228
- end
229
-
230
- def _store(opt)
231
- opt[:height] = @height if @height
232
- super
233
- end
234
- end
235
-
236
- module Padding
237
- # Text padding within the element.
238
- #
239
- # @return [Array<Integer>] top, right, bottom, left
240
- attr_reader :padding
241
-
242
- # @attribute [w] padding
243
- def padding=(*value)
244
- @padding = Utils.padding(*value).freeze
245
- end
246
-
247
- # Text top padding.
248
- #
249
- # @attribute [r] padding_top
250
- # @return [Integer]
251
- def padding_top = @padding[0]
252
-
253
- # @attribute [w] padding_top
254
- def padding_top=(value)
255
- @padding[0] = as_uint(value)
256
- end
257
-
258
- # Text right padding.
259
- #
260
- # @attribute [r] padding_right
261
- # @return [Integer]
262
- def padding_right = @padding[1]
263
-
264
- # @attribute [w] padding_right
265
- def padding_right=(value)
266
- @padding[1] = as_uint(value)
267
- end
268
-
269
- # Text bottom padding.
270
- #
271
- # @attribute [r] padding_bottom
272
- # @return [Integer]
273
- def padding_bottom = @padding[2]
274
-
275
- # @attribute [w] padding_bottom
276
- def padding_bottom=(value)
277
- @padding[2] = as_uint(value)
278
- end
279
-
280
- # Text left padding.
281
- #
282
- # @attribute [r] padding_left
283
- # @return [Integer]
284
- def padding_left = @padding[3]
285
-
286
- # @attribute [w] padding_left
287
- def padding_left=(value)
288
- @padding[3] = as_uint(value)
289
- end
290
-
291
- protected
292
-
293
- def _init
294
- @padding = Array.new(4, 0)
295
- super
296
- end
297
-
298
- def _assign(opt)
299
- self.padding = opt[:padding] if opt.key?(:padding)
300
- @padding[0] = as_uint(opt[:padding_top]) if opt.key?(:padding_top)
301
- @padding[1] = as_uint(opt[:padding_right]) if opt.key?(:padding_right)
302
- @padding[2] = as_uint(opt[:padding_bottom]) if opt.key?(:padding_bottom)
303
- @padding[3] = as_uint(opt[:padding_left]) if opt.key?(:padding_left)
304
- super
305
- end
306
-
307
- def _store(opt)
308
- val = @padding.dup
309
- if val[1] == val[3]
310
- val.pop
311
- if val[0] == val[2]
312
- if val[0] == val[1]
313
- opt[:padding] = val[0] if val[0] != 0
314
- return super
315
- end
316
- val.pop
317
- end
318
- end
319
- opt[:padding] = val
320
- super
321
- end
322
-
323
- def initialize_copy(*_)
324
- super
325
- @padding = @padding.dup
326
- end
327
- end
328
-
329
- module Margin
330
- # Element margin.
331
- #
332
- # @return [Array<Integer>] [top, right, bottom, left]
333
- attr_reader :margin
334
-
335
- # @attribute [w] margin
336
- def margin=(*value)
337
- @margin = Utils.margin(*value).freeze
338
- end
339
-
340
- # Element top margin.
341
- #
342
- # @attribute [r] margin_top
343
- # @return [Integer]
344
- def margin_top = @margin[0]
345
-
346
- # @attribute [w] margin_top
347
- def margin_top=(value)
348
- @margin[0] = as_uint(value)
349
- end
350
-
351
- # Element right margin.
352
- #
353
- # @attribute [r] margin_right
354
- # @return [Integer]
355
- def margin_right = @margin[1]
356
-
357
- # @attribute [w] margin_right
358
- def margin_right=(value)
359
- @margin[1] = as_uint(value)
360
- end
361
-
362
- # Element bottom margin.
363
- #
364
- # @attribute [r] margin_bottom
365
- # @return [Integer]
366
- def margin_bottom = @margin[2]
367
-
368
- # @attribute [w] margin_bottom
369
- def margin_bottom=(value)
370
- @margin[2] = as_uint(value)
371
- end
372
-
373
- # Element left margin.
374
- #
375
- # @attribute [r] margin_left
376
- # @return [Integer]
377
- def margin_left = @margin[3]
378
-
379
- # @attribute [w] margin_left
380
- def margin_left=(value)
381
- @margin[3] = as_uint(value)
382
- end
383
-
384
- protected
385
-
386
- def _init
387
- @margin = Array.new(4, 0)
388
- super
389
- end
390
-
391
- def _assign(opt)
392
- self.margin = opt[:margin] if opt.key?(:margin)
393
- @margin[0] = as_uint(opt[:margin_top]) if opt.key?(:margin_top)
394
- @margin[1] = as_uint(opt[:margin_right]) if opt.key?(:margin_right)
395
- @margin[2] = as_uint(opt[:margin_bottom]) if opt.key?(:margin_bottom)
396
- @margin[3] = as_uint(opt[:margin_left]) if opt.key?(:margin_left)
397
- super
398
- end
399
-
400
- def _store(opt)
401
- val = @margin.dup
402
- if val[1] == val[3]
403
- val.pop
404
- if val[0] == val[2]
405
- if val[0] == val[1]
406
- opt[:margin] = val[0] if val[0] != 0
407
- return super
408
- end
409
- val.pop
410
- end
411
- end
412
- opt[:margin] = val
413
- super
414
- end
415
-
416
- def initialize_copy(*_)
417
- super
418
- @margin = @margin.dup
419
- end
420
- end
421
-
422
- module Style
423
- # Text style.
424
- #
425
- # @return [Array, nil]
426
- attr_reader :style
427
-
428
- # @attribute [w] style
429
- def style=(value)
430
- @style = Utils.style(value)
431
- end
432
-
433
- def style_bbcode
434
- "[#{@style.join(' ')}]" if @style
435
- end
436
-
437
- protected
438
-
439
- def _assign(opt)
440
- @style = Utils.style(opt[:style]) if opt.key?(:style)
441
- super
442
- end
443
-
444
- def _store(opt)
445
- opt[:style] = @style if @style
446
- super
447
- end
448
- end
449
-
450
- module BorderStyle
451
- # Border style.
452
- #
453
- # @return [Array, nil]
454
- attr_reader :border_style
455
-
456
- # @attribute [w] border_style
457
- def border_style=(value)
458
- @border_style = Utils.style(value)
459
- end
460
-
461
- def border_style_bbcode
462
- "[#{@border_style.join(' ')}]" if @border_style
463
- end
464
-
465
- protected
466
-
467
- def _assign(opt)
468
- @border_style = Utils.style(opt[:border_style]) if opt.key?(
469
- :border_style
470
- )
471
- super
472
- end
473
-
474
- def _store(opt)
475
- opt[:border_style] = @border_style if @border_style
476
- super
477
- end
478
- end
479
-
480
- module Border
481
- # Border type.
482
- #
483
- # @return [
484
- # :default,
485
- # :rounded,
486
- # :heavy,
487
- # :double,
488
- # :vintage,
489
- # :defaulth,
490
- # :defaultv,
491
- # :heavyh,
492
- # :heavyv,
493
- # :doubleh,
494
- # :doublev
495
- # ]
496
- # @return [nil] when element has no border
497
- attr_reader :border
498
-
499
- # @attribute [w] border
500
- def border=(value)
501
- if value
502
- @border_chars = Theme.current.border(value)
503
- @border = value
504
- else
505
- @border_chars = @border = nil
506
- end
507
- end
508
-
509
- # @private
510
- # @return [String, nil]
511
- attr_reader :border_chars
512
-
513
- protected
514
-
515
- def _assign(opt)
516
- self.border = opt[:border] if opt.key?(:border)
517
- super
518
- end
519
-
520
- def _store(opt)
521
- opt[:border] = @border if @border_chars
522
- super
523
- end
524
- end
525
- end
526
-
527
- module WithAttributes
528
- attr_reader :attributes
529
-
530
- def attributes=(value)
531
- @attributes =
532
- if value.is_a?(self.class::Attributes)
533
- value.dup
534
- elsif value.respond_to?(:to_hash)
535
- self.class::Attributes.new(**value.to_hash)
536
- else
537
- self.class::Attributes.new(**value.to_h)
538
- end
539
- end
540
-
541
- private
542
-
543
- def initialize(**attributes)
544
- @attributes = self.class::Attributes.new(**attributes)
545
- end
546
-
547
- def initialize_copy(*_)
548
- super
549
- @attributes = @attributes.dup
550
- end
551
- end
552
-
553
- private_constant :WithAttributes
554
-
555
- module TextWithAttributes
556
- include WithAttributes
557
-
558
- attr_reader :text
559
-
560
- def empty? = @text.empty?
561
- alias _to_s to_s
562
- private :_to_s
563
- def to_str = @text.join("\n")
564
- alias to_s to_str
565
-
566
- def inspect
567
- if (att = @attributes.to_hash).empty?
568
- "#{_to_s.chop} @text=#{to_s.inspect}>"
569
- else
570
- "#{_to_s.chop} @attributes=#{att} @text=#{to_s.inspect}>"
571
- end
572
- end
573
-
574
- private
575
-
576
- def initialize(*text, **attributes)
577
- @text = text
578
- @attributes =
579
- if text.last.is_a?(self.class::Attributes)
580
- text.pop.merge(**@attributes)
581
- else
582
- self.class::Attributes.new(**attributes)
583
- end
584
- end
585
-
586
- def initialize_copy(*_)
587
- super
588
- @text = @text.map(&:dup)
589
- end
590
- end
591
-
592
- private_constant :TextWithAttributes
593
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'element'
4
-
5
- module NattyUI
6
- class Choice < 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', 'Space'
17
- return @ret[@current]
18
- when 'Home'
19
- @current = 0
20
- when 'End'
21
- @current = @texts.size - 1
22
- when 'Up', 'Back', 'Shift+Tab', 'i', 'w'
23
- @current = @texts.size - 1 if (@current -= 1) < 0
24
- when 'Down', 'Tab', 'k', 's'
25
- @current = 0 if (@current += 1) == @texts.size
26
- else
27
- next true unless event.simple?
28
- c = event.key.ord
29
- @current = (c - 48).clamp(0, @texts.size - 1) if c.between?(48, 57)
30
- end
31
- next true if last == @current
32
- pin_line = NattyUI.back_to_line(pin_line, erase: false)
33
- draw
34
- last = @current
35
- end
36
- ensure
37
- NattyUI.back_to_line(@start_line)
38
- end
39
-
40
- private
41
-
42
- def initialize(parent, args, kwargs, abortable, selected)
43
- super(parent)
44
- @start_line = NattyUI.lines_written
45
- @texts = args + kwargs.values
46
- @ret = Array.new(args.size, &:itself) + kwargs.keys
47
- @abortable = abortable
48
- @current = @ret.index(selected) || 0
49
- theme = Theme.current
50
- @mark = [theme.mark(:choice), theme.choice_style]
51
- @mark_current = [theme.mark(:current_choice), theme.choice_current_style]
52
- end
53
-
54
- def draw
55
- @texts.each_with_index do |str, idx|
56
- mark, style = idx == @current ? @mark_current : @mark
57
- @parent.puts(
58
- "#{style}#{str}",
59
- first_line_prefix: mark,
60
- first_line_prefix_width: mark.width
61
- )
62
- end
63
- end
64
- end
65
-
66
- private_constant :Choice
67
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'element'
4
-
5
- module NattyUI
6
- class DumbChoice < 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
- next true unless event.simple?
13
- code = event.raw.upcase
14
- if @ret.size <= 9
15
- next true unless ('1'..'9').include?(code)
16
- code = @ret[code.ord - 49] and return code
17
- elsif ('A'..'Z').include?(code)
18
- code = @ret[code.ord - 65] and return code
19
- end
20
- end
21
- end
22
-
23
- private
24
-
25
- def draw
26
- glyph = @ret.size <= 9 ? 1 : 'A'
27
- @texts.each do |str|
28
- @parent.puts(
29
- str,
30
- first_line_prefix: "[\\#{glyph}] ",
31
- first_line_prefix_width: 4
32
- )
33
- glyph = glyph.succ
34
- end
35
- @texts = nil
36
- end
37
-
38
- def initialize(parent, args, kwargs, abortable)
39
- super(parent)
40
- @ret = Array.new(args.size, &:itself) + kwargs.keys
41
- @texts = args + kwargs.values
42
- @abortable = abortable
43
- end
44
- end
45
-
46
- private_constant :DumbChoice
47
- end