natty-ui 0.7.0 → 0.9.0

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +7 -3
  3. data/README.md +25 -47
  4. data/examples/24bit-colors.rb +27 -0
  5. data/examples/3bit-colors.rb +14 -0
  6. data/examples/8bit-colors.rb +31 -0
  7. data/examples/animate.rb +24 -0
  8. data/examples/attributes.rb +25 -159
  9. data/examples/demo.rb +53 -0
  10. data/examples/illustration.png +0 -0
  11. data/examples/illustration.rb +29 -0
  12. data/examples/{list_in_columns.rb → ls.rb} +13 -20
  13. data/examples/message.rb +30 -0
  14. data/examples/progress.rb +25 -30
  15. data/examples/query.rb +26 -28
  16. data/examples/read_key.rb +13 -0
  17. data/examples/table.rb +36 -0
  18. data/lib/natty-ui/ansi.rb +351 -305
  19. data/lib/natty-ui/ansi_constants.rb +73 -0
  20. data/lib/natty-ui/ansi_wrapper.rb +136 -162
  21. data/lib/natty-ui/features.rb +11 -18
  22. data/lib/natty-ui/key_map.rb +119 -0
  23. data/lib/natty-ui/line_animation/default.rb +35 -0
  24. data/lib/natty-ui/line_animation/matrix.rb +28 -0
  25. data/lib/natty-ui/line_animation/rainbow.rb +30 -0
  26. data/lib/natty-ui/line_animation/test.rb +29 -0
  27. data/lib/natty-ui/line_animation/type_writer.rb +64 -0
  28. data/lib/natty-ui/line_animation.rb +54 -0
  29. data/lib/natty-ui/version.rb +2 -2
  30. data/lib/natty-ui/wrapper/animate.rb +17 -0
  31. data/lib/natty-ui/wrapper/ask.rb +21 -21
  32. data/lib/natty-ui/wrapper/element.rb +19 -23
  33. data/lib/natty-ui/wrapper/framed.rb +29 -19
  34. data/lib/natty-ui/wrapper/heading.rb +26 -53
  35. data/lib/natty-ui/wrapper/horizontal_rule.rb +37 -0
  36. data/lib/natty-ui/wrapper/list_in_columns.rb +71 -12
  37. data/lib/natty-ui/wrapper/message.rb +20 -27
  38. data/lib/natty-ui/wrapper/progress.rb +40 -13
  39. data/lib/natty-ui/wrapper/query.rb +34 -31
  40. data/lib/natty-ui/wrapper/quote.rb +25 -0
  41. data/lib/natty-ui/wrapper/request.rb +21 -10
  42. data/lib/natty-ui/wrapper/section.rb +55 -39
  43. data/lib/natty-ui/wrapper/table.rb +298 -0
  44. data/lib/natty-ui/wrapper/task.rb +6 -7
  45. data/lib/natty-ui/wrapper.rb +123 -41
  46. data/lib/natty-ui.rb +65 -40
  47. metadata +28 -9
  48. data/examples/basic.rb +0 -62
@@ -11,10 +11,38 @@ module NattyUI
11
11
  # The non-compact format prints all columns in same width and order the list
12
12
  # items row-wise.
13
13
  #
14
+ # @example simple compact list
15
+ # ui.ls('apple', 'banana', 'blueberry', 'pineapple', 'strawberry')
16
+ # # => apple banana blueberry pineapple strawberry
17
+ #
18
+ # @example (unordered) list with red dot
19
+ # ui.ls('apple', 'banana', 'blueberry', 'pineapple', 'strawberry', glyph: '[[red]]•[[/]]')
20
+ # # => • apple • banana • blueberry • pineapple • strawberry
21
+ #
22
+ # @example ordered list
23
+ # ui.ls('apple', 'banana', 'blueberry', 'pineapple', 'strawberry', glyph: 1)
24
+ # # => 1 apple 2 banana 3 blueberry 4 pineapple 5 strawberry
25
+ #
26
+ # @example ordered list, start at 100
27
+ # ui.ls('apple', 'banana', 'blueberry', 'pineapple', 'strawberry', glyph: 100)
28
+ # # => 100 apple 101 banana 102 blueberry 103 pineapple 104 strawberry
29
+ #
30
+ # @example ordered list using, uppercase characters
31
+ # ui.ls('apple', 'banana', 'blueberry', 'pineapple', 'strawberry', glyph: :A)
32
+ # # => A apple B banana C blueberry D pineapple E strawberry
33
+ #
34
+ # @example ordered list, using lowercase characters
35
+ # ui.ls('apple', 'banana', 'blueberry', 'pineapple', 'strawberry', glyph: :a)
36
+ # # => a apple b banana c blueberry d pineapple e strawberry
37
+ #
14
38
  # @param [Array<#to_s>] args items to print
15
39
  # @param [Boolean] compact whether to use compact format
40
+ # @param [nil,#to_s,Integer,Symbol] glyph optional glyph used as element
41
+ # prefix
16
42
  # @return [Wrapper, Wrapper::Element] itself
17
- def ls(*args, compact: true) = _element(:ListInColumns, args, compact)
43
+ def ls(*args, compact: true, glyph: nil)
44
+ _element(:ListInColumns, args, compact, glyph)
45
+ end
18
46
  end
19
47
 
20
48
  class Wrapper
@@ -25,22 +53,52 @@ module NattyUI
25
53
  class ListInColumns < Element
26
54
  protected
27
55
 
28
- def _call(list, compact)
56
+ def call(list, compact, glyph)
57
+ return @parent if list.empty?
29
58
  list.flatten!
30
- return parent if list.empty?
31
- list.map! { |item| Item.new(item = item.to_s, _plain_width(item)) }
59
+ cvt = cvt(glyph, list.size)
60
+ list.map! do |item|
61
+ Item.new(item = cvt[item], NattyUI.display_width(item))
62
+ end
32
63
  if compact
33
- each_compacted(list, available_width) { |line| parent.puts(line) }
64
+ each_compacted(list, available_width - 1) { @parent.puts(_1) }
65
+ else
66
+ each(list, available_width - 1) { @parent.puts(_1) }
67
+ end
68
+ @parent
69
+ end
70
+
71
+ def cvt(glyph, size)
72
+ case glyph
73
+ when nil, false
74
+ ->(s) { NattyUI.embellish(s) }
75
+ when :hex
76
+ pad = size.to_s(16).size
77
+ glyph = 0
78
+ lambda do |s|
79
+ "#{(glyph += 1).to_s(16).rjust(pad, '0')} #{NattyUI.embellish(s)}"
80
+ end
81
+ when Integer
82
+ pad = (glyph + size).to_s.size
83
+ glyph -= 1
84
+ ->(s) { "#{(glyph += 1).to_s.rjust(pad)} #{NattyUI.embellish(s)}" }
85
+ when Symbol
86
+ lambda do |s|
87
+ "#{
88
+ t = glyph
89
+ glyph = glyph.succ
90
+ t
91
+ } #{NattyUI.embellish(s)}"
92
+ end
34
93
  else
35
- each(list, available_width) { |line| parent.puts(line) }
94
+ ->(s) { "#{glyph} #{NattyUI.embellish(s)}" }
36
95
  end
37
- parent
38
96
  end
39
97
 
40
98
  def each(list, max_width)
41
99
  width = list.max_by(&:width).width + 3
42
100
  list.each_slice(max_width / width) do |slice|
43
- yield(slice.map { |item| item.to_s(width) }.join)
101
+ yield(slice.map { _1.to_s(width) }.join)
44
102
  end
45
103
  end
46
104
 
@@ -58,7 +116,7 @@ module NattyUI
58
116
  widths = [list.max_by(&:width).width]
59
117
  1.upto(list.size - 1) do |slice_size|
60
118
  candidate = list.each_slice(list.size / slice_size).to_a
61
- cwidths = candidate.map { |ary| ary.max_by(&:width).width + 3 }
119
+ cwidths = candidate.map { _1.max_by(&:width).width + 3 }
62
120
  cwidths[-1] -= 3
63
121
  break if cwidths.sum > max_width
64
122
  found = candidate
@@ -68,14 +126,15 @@ module NattyUI
68
126
  end
69
127
 
70
128
  def fill(ary, size)
71
- diff = size - ary.size
72
- ary.fill(nil, ary.size, diff) if diff.positive?
129
+ (diff = size - ary.size).positive? && ary.fill(nil, ary.size, diff)
73
130
  end
74
131
 
75
132
  Item =
76
- Data.define(:str, :width) do
133
+ Struct.new(:str, :width) do
77
134
  def to_s(in_width) = "#{str}#{' ' * (in_width - width)}"
78
135
  end
136
+
137
+ private_constant :Item
79
138
  end
80
139
  end
81
140
  end
@@ -4,17 +4,17 @@ require_relative 'section'
4
4
 
5
5
  module NattyUI
6
6
  module Features
7
- # Creates a simple message section with a highlighted `title` and
8
- # prints given additional arguments as lines into the section.
7
+ # Creates a section with a highlighted `title` and prints given additional
8
+ # arguments as lines into the section.
9
9
  #
10
10
  # @param [#to_s] title object to print as section title
11
11
  # @param [Array<#to_s>] args more objects to print
12
- # @param [#to_s] symbol symbol/prefix used for the title
12
+ # @param [#to_s] glyph glyph/prefix used for the title
13
13
  # @yieldparam [Wrapper::Message] message the created section
14
14
  # @return [Object] the result of the code block
15
15
  # @return [Wrapper::Message] itself, when no code block is given
16
- def message(title, *args, symbol: :default, &block)
17
- _section(self, :Message, args, title: title, symbol: symbol, &block)
16
+ def message(title, *args, glyph: :default, &block)
17
+ _section(:Message, args, title: title, glyph: glyph, &block)
18
18
  end
19
19
  alias msg message
20
20
 
@@ -26,7 +26,7 @@ module NattyUI
26
26
  # @yieldparam (see #message)
27
27
  # @return (see #message)
28
28
  def information(title, *args, &block)
29
- _section(self, :Message, args, title: title, symbol: :information, &block)
29
+ _section(:Message, args, title: title, glyph: :information, &block)
30
30
  end
31
31
  alias info information
32
32
 
@@ -37,7 +37,7 @@ module NattyUI
37
37
  # @yieldparam (see #message)
38
38
  # @return (see #message)
39
39
  def warning(title, *args, &block)
40
- _section(self, :Message, args, title: title, symbol: :warning, &block)
40
+ _section(:Message, args, title: title, glyph: :warning, &block)
41
41
  end
42
42
  alias warn warning
43
43
 
@@ -48,7 +48,7 @@ module NattyUI
48
48
  # @yieldparam (see #message)
49
49
  # @return (see #message)
50
50
  def error(title, *args, &block)
51
- _section(self, :Message, args, title: title, symbol: :error, &block)
51
+ _section(:Message, args, title: title, glyph: :error, &block)
52
52
  end
53
53
  alias err error
54
54
 
@@ -61,7 +61,7 @@ module NattyUI
61
61
  # @yieldparam (see #message)
62
62
  # @return (see #message)
63
63
  def completed(title, *args, &block)
64
- _section(self, :Message, args, title: title, symbol: :completed, &block)
64
+ _section(:Message, args, title: title, glyph: :completed, &block)
65
65
  end
66
66
  alias done completed
67
67
  alias ok completed
@@ -76,7 +76,7 @@ module NattyUI
76
76
  # @yieldparam (see #message)
77
77
  # @return (see #message)
78
78
  def failed(title, *args, &block)
79
- _section(self, :Message, args, title: title, symbol: :failed, &block)
79
+ _section(:Message, args, title: title, glyph: :failed, &block)
80
80
  end
81
81
  end
82
82
 
@@ -93,24 +93,17 @@ module NattyUI
93
93
  class Message < Section
94
94
  protected
95
95
 
96
- def initialize(parent, title:, symbol:, **opts)
97
- parent.puts(title, **title_attr(str = as_symbol_str(symbol), symbol))
98
- super(parent, prefix: ' ' * (NattyUI.display_width(str) + 1), **opts)
96
+ def initialize(parent, title:, glyph:)
97
+ glyph = parent.wrapper.glyph(glyph) || glyph
98
+ prefix_width = NattyUI.display_width(glyph) + 1
99
+ super(
100
+ parent,
101
+ prefix: ' ' * prefix_width,
102
+ prefix_width: prefix_width,
103
+ suffix_width: 0
104
+ )
105
+ parent.puts(title, prefix: "#{glyph} ", prefix_width: prefix_width)
99
106
  end
100
-
101
- def title_attr(str, _symbol) = { prefix: "#{str} " }
102
- def as_symbol_str(symbol) = (SYMBOL[symbol] || symbol)
103
-
104
- SYMBOL = {
105
- default: '•',
106
- information: 'i',
107
- warning: '!',
108
- error: 'X',
109
- completed: '✓',
110
- failed: 'F',
111
- query: '▶︎',
112
- task: '➔'
113
- }.compare_by_identity.freeze
114
107
  end
115
108
  end
116
109
  end
@@ -7,14 +7,23 @@ module NattyUI
7
7
  module Features
8
8
  # Creates progress element implementing additional {ProgressAttributes}.
9
9
  #
10
- # A progress element has additional states and can be closed with {#completed}
11
- # or {#failed}.
10
+ # When a `max_value` is given, the progress will by displayed as a bar.
11
+ # Otherwise the `spinner` is used for a little animation.
12
+ #
13
+ # When no pre-defined spinner is specified then spinner will be
14
+ # used char-wise as a string for the progress animation.
15
+ #
16
+ # A progress element has additional states and can be closed with
17
+ # {#completed} or {#failed}.
12
18
  #
13
19
  # @param [#to_s] title object to print as progress title
14
- # @param [##to_f] max_value maximum value of the progress
20
+ # @param [#to_f] max_value maximum value of the progress
21
+ # @param [:bar, :blink, :blocks, :braile, :circle, :colors, :pulse,
22
+ # :snake, :swap, :triangles, :vintage, #to_s] spinner type of spinner or
23
+ # spinner elements
15
24
  # @return [Wrapper::Progress] the created progress element
16
- def progress(title, max_value: nil)
17
- _section(self, :Progress, nil, title: title, max_value: max_value)
25
+ def progress(title, max_value: nil, spinner: :pulse)
26
+ _element(:Progress, title, max_value, spinner)
18
27
  end
19
28
  end
20
29
 
@@ -29,16 +38,34 @@ module NattyUI
29
38
 
30
39
  protected
31
40
 
32
- def initialize(parent, title:, max_value:, **opts)
33
- super(parent, **opts)
41
+ def call(title, max_value, spinner)
34
42
  @final_text = [title]
35
43
  @max_value = [0, max_value.to_f].max if max_value
36
- @value = 0
37
- @progress = 0
38
- draw(title)
44
+ @value = @progress = 0
45
+ draw(title, SPINNER[spinner] || spinner.to_s)
46
+ self
47
+ end
48
+
49
+ SPINNER = {
50
+ bar: '▁▂▃▄▅▆▇█▇▆▅▄▃▂',
51
+ blink: '■□▪▫',
52
+ blocks: '▖▘▝▗',
53
+ braile: '⣷⣯⣟⡿⢿⣻⣽⣾',
54
+ braile_reverse: '⡿⣟⣯⣷⣾⣽⣻⢿',
55
+ circle: '◐◓◑◒',
56
+ colors: '🟨🟧🟥🟦🟪🟩',
57
+ pulse: '•✺◉●◉✺',
58
+ snake: '⠁⠉⠙⠸⢰⣠⣄⡆⠇⠃',
59
+ swap: '㊂㊀㊁',
60
+ triangles: '◢◣◤◥',
61
+ vintage: '-\\|/'
62
+ }.compare_by_identity.freeze
63
+ private_constant :SPINNER
64
+
65
+ def draw(title, _spinner)
66
+ (wrapper.stream << @parent.prefix << "➔ #{title} ").flush
39
67
  end
40
68
 
41
- def draw(title) = (wrapper.stream << prefix << "➔ #{title} ").flush
42
69
  def end_draw = (wrapper.stream << "\n")
43
70
 
44
71
  def redraw
@@ -53,11 +80,11 @@ module NattyUI
53
80
  end_draw
54
81
  return @parent.failed(*@final_text) if failed?
55
82
  _section(
56
- @parent,
57
83
  :Message,
58
84
  @final_text,
85
+ owner: @parent,
59
86
  title: @final_text.shift,
60
- symbol: @status = :completed
87
+ glyph: @status = :completed
61
88
  )
62
89
  end
63
90
  end
@@ -7,7 +7,7 @@ module NattyUI
7
7
  # Request a choice from user.
8
8
  #
9
9
  # @example Select by Index
10
- # choice = sec.query(
10
+ # choice = ui.query(
11
11
  # 'Which fruits do you prefer?',
12
12
  # 'Apples',
13
13
  # 'Bananas',
@@ -16,7 +16,7 @@ module NattyUI
16
16
  # # => '1' or '2' or '3' or nil if user aborted
17
17
  #
18
18
  # @example Select by given char
19
- # choice = sec.query(
19
+ # choice = ui.query(
20
20
  # 'Which fruits do you prefer?',
21
21
  # a: 'Apples',
22
22
  # b: 'Bananas',
@@ -29,13 +29,14 @@ module NattyUI
29
29
  # @param question [#to_s] Question to display
30
30
  # @param choices [#to_s] choices selectable via index (0..9)
31
31
  # @param result [Symbol] defines how the result will be returned
32
+ # @param display [Symbol] display choices as `:list` or `:compact`
32
33
  # @param kw_choices [{Char => #to_s}] choices selectable with given char
33
34
  # @return [Char] when `result` is configured as `:char`
34
35
  # @return [#to_s] when `result` is configured as `:choice`
35
36
  # @return [[Char, #to_s]] when `result` is configured as `:both`
36
- # @return [nil] when input was aborted with `ESC`, `^C` or `^D`
37
- def query(question, *choices, result: :char, **kw_choices)
38
- _element(:Query, question, choices, kw_choices, result)
37
+ # @return [nil] when input was aborted with `^C` or `^D`
38
+ def query(question, *choices, result: :char, display: :list, **kw_choices)
39
+ _element(:Query, question, choices, kw_choices, result, display)
39
40
  end
40
41
  end
41
42
 
@@ -47,41 +48,43 @@ module NattyUI
47
48
  class Query < Element
48
49
  protected
49
50
 
50
- def _call(question, choices, kw_choices, result_type)
51
- choices = grab(choices, kw_choices)
52
- return if choices.empty?
53
- wrapper.temporary do
54
- _section(
55
- @parent,
56
- :Message,
57
- choices.map { |k, v| "#{k} #{v}" },
58
- title: question,
59
- symbol: :query
60
- )
61
- read(choices, result_type)
51
+ def call(question, choices, kw_choices, result, display)
52
+ return if choices.empty? && kw_choices.empty?
53
+ choices = as_choices(choices, kw_choices)
54
+ text = choices.map { |k, v| "⦗#{CHOICE_MARK}#{k}#{Ansi::RESET}⦘ #{v}" }
55
+ @parent.wrapper.temporary do
56
+ if display == :compact
57
+ @parent.msg(question, glyph: :query).ls(text)
58
+ else
59
+ @parent.msg(question, *text, glyph: :query)
60
+ end
61
+ read(choices, result)
62
62
  end
63
63
  end
64
64
 
65
- def read(choices, result_type)
65
+ def as_choices(choices, kw_choices)
66
+ choices
67
+ .take(9)
68
+ .each_with_index
69
+ .to_h { |str, i| [i + 1, str] }
70
+ .merge!(kw_choices)
71
+ .transform_keys!(&:to_s)
72
+ .transform_values! { _1.to_s.gsub(/[[:space:]]/, ' ') }
73
+ end
74
+
75
+ def read(choices, result)
66
76
  while true
67
- char = NattyUI.in_stream.getch
68
- return if "\3\4\e".include?(char)
77
+ char = NattyUI.read_key
78
+ return if char == 'Ctrl+C'
69
79
  next unless choices.key?(char)
70
- return char if result_type == :char
71
- return choices[char] if result_type == :choice
80
+ return char if result == :char
81
+ return choices[char] if result == :title
72
82
  return char, choices[char]
73
83
  end
74
84
  end
75
85
 
76
- def grab(choices, kw_choices)
77
- Array
78
- .new(choices.size) { |i| i + 1 }
79
- .zip(choices)
80
- .to_h
81
- .merge!(kw_choices)
82
- .transform_keys! { |k| [k.to_s[0], ' '].max }
83
- .transform_values! { |v| v.to_s.tr("\r\n\t", ' ') }
84
- end
86
+ CHOICE_MARK = Ansi[:bold, 34]
87
+ private_constant :CHOICE_MARK
85
88
  end
86
89
  end
87
90
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'element'
4
+
5
+ module NattyUI
6
+ module Features
7
+ # Creates a quotation section and prints given arguments as lines
8
+ # into the section.
9
+ #
10
+ # @param (see #section)
11
+ # @yieldparam (see #section)
12
+ # @return [Object] the result of the code block
13
+ # @return [Wrapper::Quote] itself, when no code block is given
14
+ def quote(*args, &block) = _section(:Quote, args, prefix: '▍ ', &block)
15
+ end
16
+
17
+ class Wrapper
18
+ #
19
+ # A quotation {Section}.
20
+ #
21
+ # @see Features#quote
22
+ class Quote < Section
23
+ end
24
+ end
25
+ end
@@ -23,21 +23,32 @@ module NattyUI
23
23
  class Request < Element
24
24
  protected
25
25
 
26
- def _call(question, password)
27
- return read_password(question) if password
28
- NattyUI.readline(prompt(question), stream: wrapper.stream)
26
+ def call(question, password)
27
+ draw(question)
28
+ return NattyUI.in_stream.getpass if password
29
+ NattyUI.in_stream.gets(chomp: true)
30
+ rescue Interrupt, SystemCallError
31
+ nil
29
32
  ensure
30
- finish
33
+ (wrapper = @parent.wrapper).ansi? and
34
+ (wrapper.stream << ANSI_FINISH).flush
31
35
  end
32
36
 
33
- def read_password(question)
34
- (wrapper.stream << prompt(question)).flush
35
- NattyUI.in_stream.getpass
36
- rescue Interrupt
37
- nil
37
+ def draw(question)
38
+ wrapper = @parent.wrapper
39
+ glyph = wrapper.glyph(:query)
40
+ @parent.print(
41
+ question,
42
+ prefix: "#{glyph} #{Ansi[255]}",
43
+ prefix_width: NattyUI.display_width(glyph) + 1,
44
+ suffix_width: 0
45
+ )
46
+ (wrapper.stream << ANSI_PREFIX).flush if wrapper.ansi?
38
47
  end
39
48
 
40
- def prompt(question) = "#{prefix}▶︎ #{question}: "
49
+ ANSI_PREFIX = Ansi::RESET + Ansi::ITALIC
50
+ ANSI_FINISH = Ansi::RESET + Ansi::LINE_PREVIOUS + Ansi::LINE_ERASE
51
+ private_constant :ANSI_PREFIX, :ANSI_FINISH
41
52
  end
42
53
  end
43
54
  end
@@ -7,24 +7,16 @@ module NattyUI
7
7
  # Creates a default section and prints given arguments as lines
8
8
  # into the section.
9
9
  #
10
- # @param [Array<#to_s>] args objects to print
10
+ # @param [Array<#to_s>] args optional objects to print
11
+ # @param [String] prefix used for each printed line
12
+ # @param [String] suffix used for each printed line
11
13
  # @yieldparam [Wrapper::Section] section the created section
12
14
  # @return [Object] the result of the code block
13
15
  # @return [Wrapper::Section] itself, when no code block is given
14
- def section(*args, &block)
15
- _section(self, :Section, args, prefix: ' ', suffix: ' ', &block)
16
+ def section(*args, prefix: ' ', suffix: ' ', &block)
17
+ _section(:Section, args, prefix: prefix, suffix: suffix, &block)
16
18
  end
17
19
  alias sec section
18
-
19
- # Creates a quotation section and prints given arguments as lines
20
- # into the section.
21
- #
22
- # @param (see #section)
23
- # @yieldparam (see #section)
24
- # @return (see #section)
25
- def quote(*args, &block)
26
- _section(self, :Section, args, prefix: '▍ ', prefix_attr: 39, &block)
27
- end
28
20
  end
29
21
 
30
22
  class Wrapper
@@ -34,47 +26,57 @@ module NattyUI
34
26
  # A section can contain other elements and sections.
35
27
  #
36
28
  # @see Features#section
37
- # @see Features#quote
38
29
  class Section < Element
39
- # Close the section.
40
- #
41
- # @return [Section] itself
42
- def close = _close(:closed)
30
+ # @return [Integer] available columns count within the section
31
+ def available_width
32
+ @available_width ||=
33
+ @parent.available_width - @prefix_width - @suffix_width
34
+ end
43
35
 
44
- # Print given arguments as lines into the section.
45
- # Optionally limit the line width to given `max_width`.
36
+ # Print given arguments line-wise into the section.
46
37
  #
47
- # @overload puts(..., max_width: nil)
38
+ # @overload puts(...)
48
39
  # @param [#to_s] ... objects to print
49
- # @param [Integer, nil] max_width maximum line width
50
- # @comment @param [#to_s, nil] prefix line prefix
51
- # @comment @param [#to_s, nil] suffix line suffix
52
40
  # @return [Section] itself
53
- def puts(*args, max_width: nil, prefix: nil, suffix: nil)
41
+ def puts(*args, **kwargs)
54
42
  return self if @status
55
43
  @parent.puts(
56
44
  *args,
57
- max_width: max_width,
58
- prefix: prefix ? "#{@prefix}#{prefix}" : @prefix,
59
- suffix: suffix ? "#{suffix}#{@suffix}" : @suffix
45
+ **kwargs.merge!(
46
+ prefix: "#{@prefix}#{kwargs[:prefix]}",
47
+ prefix_width: @prefix_width + kwargs[:prefix_width].to_i,
48
+ suffix: "#{kwargs[:suffix]}#{@suffix}",
49
+ suffix_width: @suffix_width + kwargs[:suffix_width].to_i
50
+ )
60
51
  )
61
52
  self
62
53
  end
63
- alias add puts
64
54
 
65
- # Add at least one empty line
55
+ # Print given arguments into the section.
66
56
  #
67
- # @param [#to_i] lines count of lines
68
- # @return [Section] itself
69
- def space(lines = 1)
70
- @parent.puts(
71
- "\n" * [lines.to_i, 1].max,
72
- prefix: @prefix,
73
- suffix: @suffix
57
+ # @overload print(...)
58
+ # @param [#to_s] ... objects to print
59
+ # @return [Section] itself
60
+ def print(*args, **kwargs)
61
+ return self if @status
62
+ @parent.print(
63
+ *args,
64
+ **kwargs.merge!(
65
+ prefix: "#{@prefix}#{kwargs[:prefix]}",
66
+ prefix_width: @prefix_width + kwargs[:prefix_width].to_i,
67
+ suffix: "#{kwargs[:suffix]}#{@suffix}",
68
+ suffix_width: @suffix_width + kwargs[:suffix_width].to_i
69
+ )
74
70
  )
75
71
  self
76
72
  end
77
73
 
74
+ # Add at least one empty line
75
+ #
76
+ # @param [#to_i] lines count of lines
77
+ # @return [Section] itself
78
+ def space(lines = 1) = puts("\n" * [1, lines.to_i].max)
79
+
78
80
  # @note The screen manipulation is only available in ANSI mode see {#ansi?}
79
81
  #
80
82
  # Resets the part of the screen written below the current output line when
@@ -90,12 +92,26 @@ module NattyUI
90
92
  # @return [Object] block result
91
93
  def temporary = block_given? ? yield(self) : self
92
94
 
95
+ # @!visibility private
96
+ def inspect = @status ? "#{_to_s[..-2]} status=#{@status}>" : _to_s
97
+
98
+ # @!visibility private
99
+ def rcol = @parent.rcol - @suffix_width
100
+
93
101
  protected
94
102
 
95
- def initialize(parent, prefix: nil, suffix: nil, **opts)
96
- super(parent, **opts)
103
+ def initialize(
104
+ parent,
105
+ prefix:,
106
+ prefix_width: NattyUI.display_width(prefix),
107
+ suffix: nil,
108
+ suffix_width: NattyUI.display_width(suffix)
109
+ )
110
+ super(parent)
97
111
  @prefix = prefix
98
112
  @suffix = suffix
113
+ @prefix_width = prefix_width
114
+ @suffix_width = suffix_width
99
115
  end
100
116
  end
101
117
  end