natty-ui 0.9.4 → 0.11.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -11
  3. data/examples/24bit-colors.rb +8 -16
  4. data/examples/3bit-colors.rb +1 -1
  5. data/examples/8bit-colors.rb +15 -11
  6. data/examples/animate.rb +31 -11
  7. data/examples/attributes.rb +16 -14
  8. data/examples/attributes_list.rb +14 -0
  9. data/examples/demo.rb +4 -4
  10. data/examples/illustration.rb +52 -24
  11. data/examples/ls.rb +4 -4
  12. data/examples/message.rb +11 -9
  13. data/examples/progress.rb +2 -2
  14. data/examples/query.rb +1 -1
  15. data/examples/table.rb +23 -23
  16. data/lib/natty-ui/animation/binary.rb +37 -0
  17. data/lib/natty-ui/animation/default.rb +42 -0
  18. data/lib/natty-ui/animation/matrix.rb +55 -0
  19. data/lib/natty-ui/animation/rainbow.rb +28 -0
  20. data/lib/natty-ui/animation/type_writer.rb +44 -0
  21. data/lib/natty-ui/animation.rb +69 -0
  22. data/lib/natty-ui/ansi/constants.rb +1 -1
  23. data/lib/natty-ui/ansi.rb +46 -26
  24. data/lib/natty-ui/ansi_wrapper.rb +47 -51
  25. data/lib/natty-ui/key_map.rb +72 -49
  26. data/lib/natty-ui/preload.rb +1 -1
  27. data/lib/natty-ui/text.rb +75 -85
  28. data/lib/natty-ui/version.rb +1 -1
  29. data/lib/natty-ui/wrapper/animate.rb +1 -1
  30. data/lib/natty-ui/wrapper/ask.rb +1 -1
  31. data/lib/natty-ui/wrapper/element.rb +18 -10
  32. data/lib/natty-ui/wrapper/features.rb +0 -8
  33. data/lib/natty-ui/wrapper/framed.rb +7 -27
  34. data/lib/natty-ui/wrapper/message.rb +1 -1
  35. data/lib/natty-ui/wrapper/request.rb +1 -1
  36. data/lib/natty-ui/wrapper/section.rb +18 -18
  37. data/lib/natty-ui/wrapper/table.rb +432 -168
  38. data/lib/natty-ui/wrapper.rb +45 -48
  39. data/lib/natty-ui.rb +65 -2
  40. metadata +10 -8
  41. data/lib/natty-ui/line_animation/default.rb +0 -35
  42. data/lib/natty-ui/line_animation/matrix.rb +0 -28
  43. data/lib/natty-ui/line_animation/rainbow.rb +0 -30
  44. data/lib/natty-ui/line_animation/type_writer.rb +0 -44
  45. data/lib/natty-ui/line_animation.rb +0 -48
data/lib/natty-ui/text.rb CHANGED
@@ -5,50 +5,41 @@ require_relative 'ansi'
5
5
  module NattyUI
6
6
  module Text
7
7
  class << self
8
- def plain(str) = Ansi.blemish(plain_but_ansi(str))
9
-
10
8
  def plain_but_ansi(str)
11
9
  (str = str.to_s).empty? and return str
12
- str.gsub(/(\[\[((?~\]\]))\]\])/) do
13
- match = Regexp.last_match[2]
14
- next match.size == 1 ? nil : "[[#{match[1..]}]]" if match[0] == '/'
15
- Ansi.try_convert(match) ? nil : "[[#{match}]]"
10
+ str.gsub(BBCODE) do
11
+ match = Regexp.last_match[1]
12
+ if match[0] == '/'
13
+ next if match.size == 1
14
+ next "[#{match[1..]}]" if match[1] == '/'
15
+ end
16
+ Ansi.try_convert(match) ? nil : "[#{match}]"
16
17
  end
17
18
  end
18
19
 
20
+ def plain(str) = Ansi.blemish(plain_but_ansi(str))
21
+
19
22
  def embellish(str)
20
23
  (str = str.to_s).empty? and return str
21
24
  reset = false
22
25
  str =
23
- str.gsub(/(\[\[((?~\]\]))\]\])/) do
24
- match = Regexp.last_match[2]
26
+ str.gsub(BBCODE) do
27
+ match = Regexp.last_match[1]
25
28
  if match[0] == '/'
26
- next "[[#{match[1..]}]]" if match.size > 1
27
- reset = false
28
- next Ansi::RESET
29
+ if match.size == 1
30
+ reset = false
31
+ next Ansi::RESET
32
+ end
33
+ next "[#{match[1..]}]" if match[1] == '/'
29
34
  end
35
+
30
36
  ansi = Ansi.try_convert(match)
31
- ansi ? reset = ansi : "[[#{match}]]"
37
+ ansi ? reset = ansi : "[#{match}]"
32
38
  end
33
39
  reset ? "#{str}#{Ansi::RESET}" : str
34
40
  end
35
41
 
36
- def width(str)
37
- return 0 if (str = plain(str)).empty?
38
- str = str.encode(UTF_8) if str.encoding != UTF_8
39
- width = 0
40
- in_zero_width = false
41
- str.scan(WIDTH_SCANNER) do |np_start, np_end, _csi, _osc, gc|
42
- if in_zero_width
43
- in_zero_width = false if np_end
44
- next
45
- end
46
- next in_zero_width = true if np_start
47
- width += char_width(gc) if gc
48
- end
49
- width
50
- end
51
-
42
+ # works for UTF-8 chars only!
52
43
  def char_width(char)
53
44
  ord = char.ord
54
45
  return SPECIAL_CHARS[ord] || 2 if ord <= 0x1f
@@ -63,54 +54,67 @@ module NattyUI
63
54
  size
64
55
  end
65
56
 
66
- def simple_each_line(strs, &block)
67
- strs.each { _1.to_s.each_line(chomp: true, &block) }
68
- nil
57
+ def width(str)
58
+ return 0 if (str = plain_but_ansi(str)).empty?
59
+ str = str.encode(UTF_8) if str.encoding != UTF_8
60
+ width = 0
61
+ in_zero_width = false
62
+ str.scan(WIDTH_SCANNER) do |np_start, np_end, _csi, _osc, gc|
63
+ if in_zero_width
64
+ in_zero_width = false if np_end
65
+ next
66
+ end
67
+ next in_zero_width = true if np_start
68
+ width += char_width(gc) if gc
69
+ end
70
+ width
69
71
  end
70
72
 
71
- def each_line(strs, max_width)
72
- return if (max_width = max_width.to_i) <= 0
73
+ def each_line_plain(strs, max_width)
74
+ return if (max_width = max_width.to_i) < 1
73
75
  strs.each do |str|
74
- str
75
- .to_s
76
- .each_line(chomp: true) do |line|
77
- next yield(line) if line.empty?
78
- current = String.new(encoding: line.encoding)
79
- seq = current.dup
80
- width = 0
81
- in_zero_width = false
82
- line = line.encode(UTF_8) if line.encoding != UTF_8
83
- line.scan(WIDTH_SCANNER) do |np_start, np_end, csi, osc, gc|
84
- next in_zero_width = (current << "\1") if np_start
85
- next in_zero_width = !(current << "\2") if np_end
86
- next (current << osc) && (seq << osc) if osc
87
- if csi
88
- current << csi
89
- next if in_zero_width
90
- next seq.clear if csi == "\e[m" || csi == "\e[0m"
91
- next seq << csi
92
- end
93
- next current << gc if in_zero_width
94
- cw = char_width(gc)
95
- if (width += cw) > max_width
96
- yield(current)
97
- width = cw
98
- current = seq.dup
99
- end
100
- current << gc
76
+ plain_but_ansi(str).each_line(chomp: true) do |line|
77
+ next yield(line, 0) if line.empty?
78
+ empty = String.new(encoding: line.encoding)
79
+ current = empty.dup
80
+ width = 0
81
+ in_zero_width = false
82
+ line = line.encode(UTF_8) if line.encoding != UTF_8
83
+ line.scan(WIDTH_SCANNER) do |np_start, np_end, csi, osc, gc|
84
+ next in_zero_width = (current << "\1") if np_start
85
+ next in_zero_width = !(current << "\2") if np_end
86
+ next if osc || csi
87
+ next current << gc if in_zero_width
88
+ cw = char_width(gc)
89
+ if (width += cw) > max_width
90
+ yield(current, width - cw)
91
+ width = cw
92
+ current = empty.dup
101
93
  end
102
- yield(current)
94
+ current << gc
103
95
  end
96
+ yield(current, width)
97
+ end
104
98
  end
105
99
  nil
106
100
  end
107
101
 
108
- def each_line_with_size(strs, max_width)
109
- return if (max_width = max_width.to_i) <= 0
102
+ def as_lines_plain(strs, width, height = nil)
103
+ ret = []
104
+ each_line_plain(strs, width) do |*info|
105
+ ret << info
106
+ break if height == ret.size
107
+ end
108
+ ret
109
+ end
110
+
111
+ def each_line(strs, max_width)
112
+ return if (max_width = max_width.to_i) < 1
110
113
  strs.each do |str|
111
114
  str
112
115
  .to_s
113
116
  .each_line(chomp: true) do |line|
117
+ line = embellish(line)
114
118
  next yield(line, 0) if line.empty?
115
119
  current = String.new(encoding: line.encoding)
116
120
  seq = current.dup
@@ -123,8 +127,8 @@ module NattyUI
123
127
  next (current << osc) && (seq << osc) if osc
124
128
  if csi
125
129
  current << csi
126
- next if in_zero_width
127
130
  next seq.clear if csi == "\e[m" || csi == "\e[0m"
131
+ next if in_zero_width
128
132
  next seq << csi
129
133
  end
130
134
  next current << gc if in_zero_width
@@ -139,36 +143,21 @@ module NattyUI
139
143
  yield(current, width)
140
144
  end
141
145
  end
142
- nil
143
- end
144
-
145
- def as_lines(strs, max_width)
146
- ret = []
147
- each_line(strs, max_width) { ret << _1 }
148
- ret
149
146
  end
150
147
 
151
- def prepare_print(args, kwargs, screen_columns, &cvt)
152
- prefix = kwargs[:prefix] and prefix = prefix.empty? ? '' : cvt[prefix]
153
- suffix = kwargs[:suffix] and suffix = suffix.empty? ? '' : cvt[suffix]
154
- return ["#{prefix}#{suffix}"] if args.empty?
148
+ def as_lines(strs, width, height = nil)
155
149
  ret = []
156
- each_line(
157
- args.map!(&cvt),
158
- kwargs.fetch(:max_width) do
159
- screen_columns.call -
160
- kwargs.fetch(:prefix_width) { width(prefix) } -
161
- kwargs.fetch(:suffix_width) { width(suffix) }
162
- end
163
- ) { ret << "#{prefix}#{_1}#{suffix}" }
150
+ each_line(strs, width) do |*info|
151
+ ret << info
152
+ break if height == ret.size
153
+ end
164
154
  ret
165
155
  end
166
156
  end
167
157
 
168
158
  UTF_8 = Encoding::UTF_8
169
-
159
+ BBCODE = /(?:\[((?~[\[\]]))\])/
170
160
  WIDTH_SCANNER = /\G(?:(\1)|(\2)|(#{Ansi::CSI})|(#{Ansi::OSC})|(\X))/
171
-
172
161
  SPECIAL_CHARS = {
173
162
  0x00 => 0,
174
163
  0x01 => 1,
@@ -205,6 +194,7 @@ module NattyUI
205
194
  }.compare_by_identity.freeze
206
195
 
207
196
  autoload(:EastAsianWidth, File.join(__dir__, 'text', 'east_asian_width'))
197
+ private_constant :EastAsianWidth
208
198
 
209
199
  @ambiguous_char_width = 1
210
200
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module NattyUI
4
4
  # The version number of the gem.
5
- VERSION = '0.9.4'
5
+ VERSION = '0.11.0'
6
6
  end
@@ -6,7 +6,7 @@ module NattyUI
6
6
  #
7
7
  # @overload animate(..., animation: :default)
8
8
  # @param [#to_s] ... objects to print
9
- # @param [:default, :matrix, :rainbow, :type_writer]
9
+ # @param [:binary, :default, :matrix, :rainbow, :type_writer]
10
10
  # animation type of animation
11
11
  # @return [Wrapper::Section, Wrapper] it's parent object
12
12
  def animate(*args, **kwargs)
@@ -56,7 +56,7 @@ module NattyUI
56
56
  end
57
57
 
58
58
  def draw(question)
59
- glyph = wrapper.glyph(:query)
59
+ glyph = NattyUI.glyph(:query)
60
60
  @parent.print(
61
61
  question,
62
62
  prefix: "#{glyph} #{Ansi[255]}",
@@ -5,25 +5,33 @@ require_relative 'features'
5
5
  module NattyUI
6
6
  class Wrapper
7
7
  #
8
- # Basic visual element implementing all {Features}.
8
+ # Basic visual element.
9
9
  #
10
10
  class Element
11
11
  include Features
12
12
 
13
- # @return [Element] when embedded in a section
14
- # @return [Wrapper] when not embedded in a section
15
- attr_reader :parent
16
-
17
- # @return [Symbol] close status when closed
18
- # @return [nil] when not closed
19
- attr_reader :status
13
+ # @attribute [r] available_width
14
+ # @return [Integer] available columns count within the element
15
+ def available_width = @parent.available_width
20
16
 
21
17
  # @attribute [r] closed?
22
18
  # @return [Boolean] whether its closed or not
23
19
  def closed? = (@status != nil)
24
20
 
25
- # @return [Integer] available columns count within the element
26
- def available_width = @parent.available_width
21
+ # @return [Section, Wrapper] parent element
22
+ attr_reader :parent
23
+
24
+ # @return [Symbol, nil] status when closed
25
+ attr_reader :status
26
+
27
+ # @attribute [r] wrapper
28
+ # @return [Wrapper] assigned output stream wrapper
29
+ def wrapper
30
+ return @wrapper if @wrapper
31
+ @wrapper = @parent
32
+ @wrapper = @wrapper.parent until @wrapper.is_a?(Wrapper)
33
+ @wrapper
34
+ end
27
35
 
28
36
  # Close the element.
29
37
  #
@@ -5,14 +5,6 @@ module NattyUI
5
5
  # Features of {NattyUI} - methods to display natty elements.
6
6
  #
7
7
  module Features
8
- # @return [Wrapper] assigned output stream wrapper
9
- def wrapper
10
- return @wrapper if @wrapper
11
- @wrapper = @parent
12
- @wrapper = @wrapper.parent until @wrapper.is_a?(Wrapper)
13
- @wrapper
14
- end
15
-
16
8
  protected
17
9
 
18
10
  def _element(type, ...)
@@ -11,12 +11,12 @@ module NattyUI
11
11
  # {Wrapper::Element#close}.
12
12
  #
13
13
  # @param [Array<#to_s>] args more objects to print
14
- # @param [:block, :double, :heavy, :rounded, :semi, :simple] type frame type
14
+ # @param [:double, :heavy, :rounded, :semi, :simple] type frame type
15
15
  # @yieldparam [Wrapper::Framed] framed the created section
16
16
  # @return [Object] the result of the code block
17
17
  # @return [Wrapper::Framed] itself, when no code block is given
18
18
  def framed(*args, type: :rounded, &block)
19
- _section(:Framed, args, type: type, &block)
19
+ _section(:Framed, args, type: NattyUI.frame(type), &block)
20
20
  end
21
21
  end
22
22
 
@@ -29,37 +29,17 @@ module NattyUI
29
29
  protected
30
30
 
31
31
  def initialize(parent, type:)
32
- deco = as_deco(type)
33
- super(parent, prefix: "#{deco[0]} ", prefix_width: 2, suffix_width: 2)
34
- init(deco)
32
+ super(parent, prefix: "#{type[4]} ", prefix_width: 2, suffix_width: 2)
33
+ init(type)
35
34
  end
36
35
 
37
- def as_deco(type)
38
- if type.is_a?(Symbol)
39
- ret = DECO[type] and return ret
40
- elsif type.is_a?(String)
41
- return type if type.size == 8
42
- return type * 8 if type.size == 1
43
- end
44
- raise(ArgumentError, "invalid frame type - #{type.inspect}")
45
- end
46
-
47
- def init(deco)
36
+ def init(type)
48
37
  aw = @parent.available_width - 1
49
- parent.puts("#{deco[1]}#{deco[2] * aw}")
50
- @finish = "#{deco[5]}#{deco[6] * aw}"
38
+ parent.puts("#{type[0]}#{type[5] * aw}")
39
+ @finish = "#{type[2]}#{type[5] * aw}"
51
40
  end
52
41
 
53
42
  def finish = @parent.puts(@finish)
54
-
55
- DECO = {
56
- rounded: '│╭─╮│╰─╯',
57
- simple: '│┌─┐│└─┘',
58
- heavy: '┃┏━┓┃┗━┛',
59
- double: '║╔═╗║╚═╝',
60
- semi: '│╒═╕│╘═╛',
61
- block: '▌▛▀▜▐▙▄▟'
62
- }.compare_by_identity.freeze
63
43
  end
64
44
  end
65
45
  end
@@ -94,7 +94,7 @@ module NattyUI
94
94
  protected
95
95
 
96
96
  def initialize(parent, title:, glyph:)
97
- glyph = parent.wrapper.glyph(glyph) || glyph
97
+ glyph = NattyUI.glyph(glyph) || glyph
98
98
  prefix_width = Text.width(glyph) + 1
99
99
  super(
100
100
  parent,
@@ -36,7 +36,7 @@ module NattyUI
36
36
 
37
37
  def draw(question)
38
38
  wrapper = @parent.wrapper
39
- glyph = wrapper.glyph(:query)
39
+ glyph = NattyUI.glyph(:query)
40
40
  @parent.print(
41
41
  question,
42
42
  prefix: "#{glyph} #{Ansi[255]}",
@@ -35,18 +35,18 @@ module NattyUI
35
35
 
36
36
  # Print given arguments line-wise into the section.
37
37
  #
38
- # @overload puts(...)
39
- # @param [#to_s] ... objects to print
40
- # @return [Section] itself
41
- def puts(*args, **kwargs)
38
+ # @param [#to_s] args objects to print
39
+ # @option options [:left, :right, :center] :align text alignment
40
+ # @return [Section] itself
41
+ def puts(*args, **options)
42
42
  return self if @status
43
43
  @parent.puts(
44
44
  *args,
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
45
+ **options.merge!(
46
+ prefix: "#{@prefix}#{options[:prefix]}",
47
+ prefix_width: @prefix_width + options[:prefix_width].to_i,
48
+ suffix: "#{options[:suffix]}#{@suffix}",
49
+ suffix_width: @suffix_width + options[:suffix_width].to_i
50
50
  )
51
51
  )
52
52
  self
@@ -54,18 +54,18 @@ module NattyUI
54
54
 
55
55
  # Print given arguments into the section.
56
56
  #
57
- # @overload print(...)
58
- # @param [#to_s] ... objects to print
59
- # @return [Section] itself
60
- def print(*args, **kwargs)
57
+ # @param [#to_s] args objects to print
58
+ # @option options [:left, :right, :center] :align text alignment
59
+ # @return [Section] itself
60
+ def print(*args, **options)
61
61
  return self if @status
62
62
  @parent.print(
63
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
64
+ **options.merge!(
65
+ prefix: "#{@prefix}#{options[:prefix]}",
66
+ prefix_width: @prefix_width + options[:prefix_width].to_i,
67
+ suffix: "#{options[:suffix]}#{@suffix}",
68
+ suffix_width: @suffix_width + options[:suffix_width].to_i
69
69
  )
70
70
  )
71
71
  self