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
data/examples/tasks.rb CHANGED
@@ -2,51 +2,61 @@
2
2
 
3
3
  require_relative '../lib/natty-ui'
4
4
 
5
- ui.message '[b]​ᓚᕠᗢ NattyUI[/b] [i green]Tasks[/]' do
5
+ ui.begin '[b]ᓚᕠᗢ NattyUI[/b] [i green]Tasks' do
6
6
  ui.space
7
7
  ui.puts <<~TEXT, eol: false
8
- Tasks are sections that are closed either successfully or with an error
9
- message. If successful, their content is only displayed temporarily and can
10
- consist of all other features, in particular further (sub)tasks. As an
11
- exception to this, some elements can be “pinned” as permanent content.
8
+ [i]Tasks[/i] are sections that are either completed successfully or result
9
+ in an error message. Upon successful completion, their content is displayed
10
+ only temporarily and may consist of any other elements, including additional
11
+ (sub)tasks. As an exception to this, some elements can be “pinned” as
12
+ permanent content.
12
13
  TEXT
13
14
 
14
15
  # to simulate some work:
15
- def do_something = sleep(0.25)
16
- def do_something_heavy = sleep(0.75)
16
+ simulate =
17
+ Module.new do
18
+ if ENV.key?('NO_SLEEP')
19
+ def self.work = nil
20
+ def self.heavy_work = nil
21
+ else
22
+ def self.work = sleep(0.2)
23
+ def self.heavy_work = sleep(1)
24
+ end
25
+ end
17
26
 
18
27
  ui.space
19
- ui.task 'Actualize Reading List' do
20
- ui.puts 'This is a simple which actualizes the book reading list.'
28
+ ui.task 'Update Reading List' do
29
+ ui.puts 'This is an example which actualizes the book reading list.'
21
30
 
22
31
  ui.task 'Connect to Library' do
23
- do_something
32
+ simulate.work
24
33
  ui.mark 'Server Found', mark: :checkmark
25
- ui.task('Login...') { do_something_heavy }
34
+ ui.task('Login...') { simulate.heavy_work }
26
35
  end
27
36
 
28
- ui.task('Request New Books') { do_something_heavy }
37
+ ui.task('Request New Books') { simulate.heavy_work }
29
38
 
30
- bar = ui.progress('Loading Books...', pin: true)
31
- 11.times do
32
- do_something
33
- bar.step
39
+ ui.progress('Loading Books...') do |progress|
40
+ while progress.value < 17
41
+ simulate.work
42
+ progress.step
43
+ end
34
44
  end
35
- bar.ok 'Books Loaded'
45
+ ui.ok '17 Books Loaded'
36
46
 
37
- ui.task('Disconnect from Library') { do_something_heavy }
47
+ ui.task('Disconnect from Library') { simulate.heavy_work }
38
48
 
39
- ui.progress 'Read Cover Images', max: 11 do |progress|
49
+ ui.progress 'Read Cover Images', max: 13 do |progress|
40
50
  while progress.value < progress.max
41
- do_something
51
+ simulate.work
42
52
  progress.step
43
53
  end
44
54
  end
45
55
 
46
56
  ui.pin 'New Books Marked', mark: :checkmark
47
57
 
48
- ui.task('Optimize Database') { do_something_heavy }
58
+ ui.task('Optimize Database') { simulate.heavy_work }
49
59
 
50
- ui.task('Remove Dust') { do_something_heavy }
60
+ ui.task('Remove Dust') { simulate.heavy_work }
51
61
  end
52
62
  end
data/examples/vbars.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative '../lib/natty-ui'
4
4
 
5
- ui.message '[b]​ᓚᕠᗢ NattyUI[/b] [i green]Print Vertical Bars[/]' do
5
+ ui.begin '[b]ᓚᕠᗢ NattyUI[/b] [i green]Print Vertical Bars' do
6
6
  values = [
7
7
  11.97,
8
8
  14.35,
@@ -27,13 +27,15 @@ ui.message '[b]​ᓚᕠᗢ NattyUI[/b] [i green]Print Vertical Bars[/]' do
27
27
  ].freeze
28
28
 
29
29
  ui.space
30
- ui.vbars values, style: :blue
30
+ ui.vbars values, style: :blue, bar_width: :auto
31
31
  ui.puts 'NattyUI can quick dump values as vertical bars.'
32
32
 
33
33
  ui.space
34
- ui.vbars values, style: :green, normalize: true, bar_width: 2
34
+ ui.vbars values, style: :green, normalize: true
35
35
  ui.puts <<~INFO, eol: false
36
36
  These are the same values but [i]normalized[/i]
37
37
  and printed with a fixed bar width.
38
38
  INFO
39
+
40
+ ui.space
39
41
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'element'
4
+
5
+ module NattyUI
6
+ # @private
7
+ class DumbProgress < Element
8
+ # @return [Numeric]
9
+ attr_reader :max
10
+
11
+ # @return [Numeric]
12
+ attr_reader :value
13
+
14
+ # @attribute [w] value
15
+ def value=(val)
16
+ return if @done != 0
17
+ return if @value == (val = @max ? val.clamp(0, @max) : [0, val].max)
18
+ @value = val
19
+ draw_bar if @max
20
+ end
21
+
22
+ def update(*title, value: nil, **)
23
+ return if @done != 0
24
+ draw_title(*title, **) unless title.empty?
25
+ if value
26
+ self.value = value
27
+ elsif @max
28
+ draw_bar
29
+ end
30
+ self
31
+ end
32
+
33
+ def step(*, count: 1, **) = update(*, value: value + count, **)
34
+
35
+ # @private
36
+ def puts(...) = @done == 0 ? super : self
37
+
38
+ private
39
+
40
+ def done
41
+ @done += 1
42
+ end
43
+
44
+ def draw_bar
45
+ diff = @value / @max.to_f
46
+ return if (fill = '█' * (20 * diff)).empty? || @last == fill
47
+ percent = diff == 1 ? '100.0%' : format('%.2f%%', 100 * diff)
48
+ percent = "0#{percent}" if percent.size < 6
49
+ @parent.puts(
50
+ "#{percent}▕#{@last = fill}#{'░' * (20 - fill.size)}▏",
51
+ prefix: @prefix
52
+ )
53
+ end
54
+
55
+ def draw_title(*, **options)
56
+ options[:cprefix] = Utils.mark[:active]
57
+ options[:prefix] = @prefix = Utils.mark[:none]
58
+ @parent.puts(*, **options)
59
+ end
60
+
61
+ def initialize(parent, max, *title, **)
62
+ super(parent)
63
+ @value = 0
64
+ @max = [0, max].max if max
65
+ draw_title(*title, **) unless title.empty?
66
+ end
67
+ end
68
+ end
@@ -3,32 +3,80 @@
3
3
  require_relative 'features'
4
4
 
5
5
  module NattyUI
6
- # Base element class supporting all {Features}.
6
+ # Base class for all UI container elements.
7
7
  #
8
+ # Instances are never created directly; they are returned (or yielded) by the
9
+ # section-opening methods on {Features}: {#section}, {#frame},
10
+ # {#task}, {#progress}, {#margin}, {#temporary}, and their typed variants.
11
+ #
12
+ # Every `Element` includes {Features}, so all output and section methods are
13
+ # available on the element itself.
14
+ #
15
+ # @example Using an element returned without a block
16
+ # frm = ui.frame 'Progress'
17
+ # frm.puts 'Loading…'
18
+ # frm.end # closes the frame
19
+ #
20
+ # @example The element is also yielded inside a block
21
+ # ui.section 'Summary' do |sec|
22
+ # sec.ok 'Done'
23
+ # end
8
24
  class Element
9
25
  include Features
10
26
 
27
+ #
28
+ # @!group Section elements
29
+ #
30
+
31
+ # Closes this element and returns the parent element.
32
+ #
33
+ # Calling `end` on an already-closed element is a no-op. All output
34
+ # methods on this element become no-ops after `end` is called.
35
+ #
36
+ # @example
37
+ # frm = ui.frame 'Results'
38
+ # ui.puts 'Content'
39
+ # frm.end # closes the frame, returns NattyUI (or the parent element)
40
+ #
41
+ # @return [Features] the parent element (or {NattyUI} if this was the
42
+ # outermost element)
43
+ def end = NattyUI.__send__(:_end, self)
44
+
45
+ #
46
+ # @!endgroup
47
+ #
48
+
11
49
  # @private
12
- def columns = @parent.columns - @prefix_width - @suffix_width
50
+ def columns
51
+ ret = @parent.columns
52
+ ret -= @prefix.width if @prefix
53
+ @suffix ? ret - @suffix.width : ret
54
+ end
13
55
 
14
56
  # @private
15
- def puts(*objects, **options)
57
+ def puts(*, **options)
16
58
  if @prefix
17
- options[:prefix] = "#{@prefix}#{options[:prefix]}"
18
- options[:prefix_width] = options[:prefix_width].to_i + @prefix_width
19
- if (first_line = options[:first_line_prefix])
20
- options[:first_line_prefix] = "#{@prefix}#{first_line}"
21
- if (flw = options[:first_line_prefix_width])
22
- options[:first_line_prefix_width] = flw + @prefix_width
23
- end
59
+ pref = options[:prefix]
60
+ if (cpref = options[:cprefix]).nil? || cpref == true
61
+ options[:prefix] = pref ? @prefix + pref : @prefix
62
+ else
63
+ cpref = StrConst[cpref] unless StrConst === cpref
64
+ options[:cprefix] = @prefix + cpref
65
+ options[:prefix] = @prefix + (pref || StrConst.spacer(cpref.width))
24
66
  end
25
67
  end
26
68
  if @suffix
27
- options[:suffix] = "#{options[:suffix]}#{@suffix}"
28
- options[:suffix_width] = options[:suffix_width].to_i + @suffix_width
69
+ suff = options[:suffix]
70
+ if (csuff = options[:csuffix]).nil? || csuff == true
71
+ options[:suffix] = suff ? suff + @suffix : @suffix
72
+ else
73
+ csuff = StrConst[csuff] unless StrConst === csuff
74
+ options[:csuffix] = csuff + @suffix
75
+ options[:suffix] = (suff || StrConst.spacer(csuff.width)) + @suffix
76
+ end
29
77
  end
30
78
  # options[:max_width] = @max_width if @max_width
31
- @parent.puts(*objects, **options)
79
+ @parent.puts(*, **options)
32
80
  self
33
81
  end
34
82
 
@@ -40,60 +88,11 @@ module NattyUI
40
88
 
41
89
  private
42
90
 
91
+ def done = raise(NotImplementedError)
92
+
43
93
  def initialize(parent)
44
94
  @parent = parent
45
- @prefix_width = @suffix_width = 0
46
- end
47
- end
48
-
49
- # Methods for elements with status like {ProgressHelper}, {Section}, {Task}.
50
- #
51
- module WithStatus
52
- # @return [Symbol, nil] element's current status
53
- attr_reader :status
54
-
55
- # @attribute [r] active?
56
- # @return [true, false] whether the element is still active
57
- def active? = @status.nil?
58
-
59
- # @attribute [r] closed?
60
- # @return [true, false] whether the element is closed
61
- def closed? = !active?
62
-
63
- # @attribute [r] closed?
64
- # @return [true, false] whether the element is completed
65
- def ok? = @state == :ok
66
-
67
- # @attribute [r] failed?
68
- # @return [true, false] whether the element is completed with failure
69
- def failed? = @state == :failed
70
-
71
- # Close the element.
72
- #
73
- # @param [#to_s] text optional message
74
- # @return [Element] itself
75
- def ok(*text)
76
- return self if @state
77
- text = [@title] if text.empty? && @title
78
- _done(text)
79
- @state = :ok
80
- self
81
- end
82
- alias done ok
83
-
84
- # Close the element with a failure.
85
- #
86
- # @param text [#to_s] optional message
87
- # @yield optionally, when block is given
88
- # @yieldparam failed [Section] see {Features.failed}
89
- # @return [Element] itself
90
- def failed(*text, &block)
91
- return self if @state
92
- _failed
93
- @state = :failed
94
- text = [@title] if text.empty? && @title
95
- @parent.failed(*text, &block)
96
- self
95
+ @done = 0
97
96
  end
98
97
  end
99
98
  end