natty-ui 0.12.0 → 0.25.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.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +23 -24
- data/examples/24bit-colors.rb +4 -9
- data/examples/3bit-colors.rb +28 -8
- data/examples/8bit-colors.rb +18 -23
- data/examples/attributes.rb +30 -25
- data/examples/cols.rb +40 -0
- data/examples/elements.rb +31 -0
- data/examples/examples.rb +45 -0
- data/examples/illustration.rb +56 -54
- data/examples/ls.rb +16 -18
- data/examples/named-colors.rb +23 -0
- data/examples/sections.rb +29 -0
- data/examples/tables.rb +62 -0
- data/examples/tasks.rb +52 -0
- data/lib/natty-ui/attributes.rb +604 -0
- data/lib/natty-ui/choice.rb +56 -0
- data/lib/natty-ui/dumb_choice.rb +45 -0
- data/lib/natty-ui/element.rb +78 -0
- data/lib/natty-ui/features.rb +798 -0
- data/lib/natty-ui/framed.rb +51 -0
- data/lib/natty-ui/ls_renderer.rb +93 -0
- data/lib/natty-ui/progress.rb +187 -0
- data/lib/natty-ui/section.rb +69 -0
- data/lib/natty-ui/table.rb +241 -0
- data/lib/natty-ui/table_renderer.rb +147 -0
- data/lib/natty-ui/task.rb +44 -0
- data/lib/natty-ui/temporary.rb +38 -0
- data/lib/natty-ui/theme.rb +303 -0
- data/lib/natty-ui/utils.rb +79 -0
- data/lib/natty-ui/version.rb +1 -1
- data/lib/natty-ui/width_finder.rb +125 -0
- data/lib/natty-ui.rb +89 -147
- metadata +47 -56
- data/examples/animate.rb +0 -44
- data/examples/attributes_list.rb +0 -14
- data/examples/demo.rb +0 -53
- data/examples/message.rb +0 -32
- data/examples/progress.rb +0 -68
- data/examples/query.rb +0 -41
- data/examples/read_key.rb +0 -13
- data/examples/table.rb +0 -41
- data/lib/natty-ui/animation/binary.rb +0 -36
- data/lib/natty-ui/animation/default.rb +0 -38
- data/lib/natty-ui/animation/matrix.rb +0 -51
- data/lib/natty-ui/animation/rainbow.rb +0 -28
- data/lib/natty-ui/animation/type_writer.rb +0 -44
- data/lib/natty-ui/animation.rb +0 -69
- data/lib/natty-ui/ansi/constants.rb +0 -75
- data/lib/natty-ui/ansi.rb +0 -521
- data/lib/natty-ui/ansi_wrapper.rb +0 -199
- data/lib/natty-ui/frame.rb +0 -53
- data/lib/natty-ui/glyph.rb +0 -64
- data/lib/natty-ui/key_map.rb +0 -142
- data/lib/natty-ui/preload.rb +0 -12
- data/lib/natty-ui/spinner.rb +0 -120
- data/lib/natty-ui/text/east_asian_width.rb +0 -2529
- data/lib/natty-ui/text.rb +0 -203
- data/lib/natty-ui/wrapper/animate.rb +0 -17
- data/lib/natty-ui/wrapper/ask.rb +0 -78
- data/lib/natty-ui/wrapper/element.rb +0 -79
- data/lib/natty-ui/wrapper/features.rb +0 -21
- data/lib/natty-ui/wrapper/framed.rb +0 -45
- data/lib/natty-ui/wrapper/heading.rb +0 -60
- data/lib/natty-ui/wrapper/horizontal_rule.rb +0 -37
- data/lib/natty-ui/wrapper/list_in_columns.rb +0 -138
- data/lib/natty-ui/wrapper/message.rb +0 -109
- data/lib/natty-ui/wrapper/mixins.rb +0 -67
- data/lib/natty-ui/wrapper/progress.rb +0 -74
- data/lib/natty-ui/wrapper/query.rb +0 -89
- data/lib/natty-ui/wrapper/quote.rb +0 -25
- data/lib/natty-ui/wrapper/request.rb +0 -54
- data/lib/natty-ui/wrapper/section.rb +0 -118
- data/lib/natty-ui/wrapper/table.rb +0 -551
- data/lib/natty-ui/wrapper/task.rb +0 -55
- data/lib/natty-ui/wrapper.rb +0 -230
data/lib/natty-ui/text.rb
DELETED
@@ -1,203 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'ansi'
|
4
|
-
|
5
|
-
module NattyUI
|
6
|
-
module Text
|
7
|
-
class << self
|
8
|
-
def plain_but_ansi(str)
|
9
|
-
(str = str.to_s).empty? and return str
|
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}]"
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def plain(str) = Ansi.blemish(plain_but_ansi(str))
|
21
|
-
|
22
|
-
def embellish(str)
|
23
|
-
(str = str.to_s).empty? and return str
|
24
|
-
reset = false
|
25
|
-
str =
|
26
|
-
str.gsub(BBCODE) do
|
27
|
-
match = Regexp.last_match[1]
|
28
|
-
if match[0] == '/'
|
29
|
-
if match.size == 1
|
30
|
-
reset = false
|
31
|
-
next Ansi::RESET
|
32
|
-
end
|
33
|
-
next "[#{match[1..]}]" if match[1] == '/'
|
34
|
-
end
|
35
|
-
|
36
|
-
ansi = Ansi.try_convert(match)
|
37
|
-
ansi ? reset = ansi : "[#{match}]"
|
38
|
-
end
|
39
|
-
reset ? "#{str}#{Ansi::RESET}" : str
|
40
|
-
end
|
41
|
-
|
42
|
-
# works for UTF-8 chars only!
|
43
|
-
def char_width(char)
|
44
|
-
ord = char.ord
|
45
|
-
return WIDTH_CONTROL_CHARS[ord] || 2 if ord < 0x20
|
46
|
-
return 1 if ord < 0xa1
|
47
|
-
size = EastAsianWidth[ord]
|
48
|
-
return @ambiguous_char_width if size == -1
|
49
|
-
if size == 1 && char.size >= 2
|
50
|
-
sco = char[1].ord
|
51
|
-
# Halfwidth Dakuten Handakuten
|
52
|
-
return sco == 0xff9e || sco == 0xff9f ? 2 : 1
|
53
|
-
end
|
54
|
-
size
|
55
|
-
end
|
56
|
-
|
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
|
71
|
-
end
|
72
|
-
|
73
|
-
def each_line_plain(strs, max_width)
|
74
|
-
return if (max_width = max_width.to_i) < 1
|
75
|
-
strs.each do |str|
|
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
|
93
|
-
end
|
94
|
-
current << gc
|
95
|
-
end
|
96
|
-
yield(current, width)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
nil
|
100
|
-
end
|
101
|
-
|
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
|
113
|
-
strs.each do |str|
|
114
|
-
str
|
115
|
-
.to_s
|
116
|
-
.each_line(chomp: true) do |line|
|
117
|
-
line = embellish(line)
|
118
|
-
next yield(line, 0) if line.empty?
|
119
|
-
current = String.new(encoding: line.encoding)
|
120
|
-
seq = current.dup
|
121
|
-
width = 0
|
122
|
-
in_zero_width = false
|
123
|
-
line = line.encode(UTF_8) if line.encoding != UTF_8
|
124
|
-
line.scan(WIDTH_SCANNER) do |np_start, np_end, csi, osc, gc|
|
125
|
-
next in_zero_width = (current << "\1") if np_start
|
126
|
-
next in_zero_width = !(current << "\2") if np_end
|
127
|
-
next (current << osc) && (seq << osc) if osc
|
128
|
-
if csi
|
129
|
-
current << csi
|
130
|
-
next seq.clear if csi == "\e[m" || csi == "\e[0m"
|
131
|
-
next if in_zero_width
|
132
|
-
next seq << csi
|
133
|
-
end
|
134
|
-
next current << gc if in_zero_width
|
135
|
-
cw = char_width(gc)
|
136
|
-
if (width += cw) > max_width
|
137
|
-
yield(current, width - cw)
|
138
|
-
width = cw
|
139
|
-
current = seq.dup
|
140
|
-
end
|
141
|
-
current << gc
|
142
|
-
end
|
143
|
-
yield(current, width)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def as_lines(strs, width, height = nil)
|
149
|
-
ret = []
|
150
|
-
each_line(strs, width) do |*info|
|
151
|
-
ret << info
|
152
|
-
break if height == ret.size
|
153
|
-
end
|
154
|
-
ret
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
UTF_8 = Encoding::UTF_8
|
159
|
-
BBCODE = /(?:\[((?~[\[\]]))\])/
|
160
|
-
WIDTH_SCANNER = /\G(?:(\1)|(\2)|(#{Ansi::CSI})|(#{Ansi::OSC})|(\X))/
|
161
|
-
WIDTH_CONTROL_CHARS = {
|
162
|
-
0x00 => 0,
|
163
|
-
0x01 => 1,
|
164
|
-
0x02 => 1,
|
165
|
-
0x03 => 1,
|
166
|
-
0x04 => 1,
|
167
|
-
0x05 => 0,
|
168
|
-
0x06 => 1,
|
169
|
-
0x07 => 0,
|
170
|
-
0x08 => 0,
|
171
|
-
0x09 => 8,
|
172
|
-
0x0a => 0,
|
173
|
-
0x0b => 0,
|
174
|
-
0x0c => 0,
|
175
|
-
0x0d => 0,
|
176
|
-
0x0e => 0,
|
177
|
-
0x0f => 0,
|
178
|
-
0x10 => 1,
|
179
|
-
0x11 => 1,
|
180
|
-
0x12 => 1,
|
181
|
-
0x13 => 1,
|
182
|
-
0x14 => 1,
|
183
|
-
0x15 => 1,
|
184
|
-
0x16 => 1,
|
185
|
-
0x17 => 1,
|
186
|
-
0x18 => 1,
|
187
|
-
0x19 => 1,
|
188
|
-
0x1a => 1,
|
189
|
-
0x1b => 1,
|
190
|
-
0x1c => 1,
|
191
|
-
0x1d => 1,
|
192
|
-
0x1e => 1,
|
193
|
-
0x1f => 1
|
194
|
-
}.compare_by_identity.freeze
|
195
|
-
|
196
|
-
autoload(:EastAsianWidth, File.join(__dir__, 'text', 'east_asian_width'))
|
197
|
-
private_constant :EastAsianWidth
|
198
|
-
|
199
|
-
@ambiguous_char_width = 1
|
200
|
-
end
|
201
|
-
|
202
|
-
private_constant :Text
|
203
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module NattyUI
|
4
|
-
module Features
|
5
|
-
# Print given arguments line-wise with animation.
|
6
|
-
#
|
7
|
-
# @overload animate(..., animation: :default)
|
8
|
-
# @param [#to_s] ... objects to print
|
9
|
-
# @param [:binary, :default, :matrix, :rainbow, :type_writer]
|
10
|
-
# animation type of animation
|
11
|
-
# @return [Wrapper::Section, Wrapper] it's parent object
|
12
|
-
def animate(*args, **kwargs)
|
13
|
-
kwargs[:animation] ||= :default
|
14
|
-
puts(*args, **kwargs)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
data/lib/natty-ui/wrapper/ask.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'element'
|
4
|
-
|
5
|
-
module NattyUI
|
6
|
-
module Features
|
7
|
-
# Ask a yes/no question from user.
|
8
|
-
#
|
9
|
-
# The defaults for `yes` and `no` will work for
|
10
|
-
# Afrikaans, Dutch, English, French, German, Italian, Polish, Portuguese,
|
11
|
-
# Romanian, Spanish and Swedish.
|
12
|
-
#
|
13
|
-
# The default for `yes` includes `ENTER` and `RETURN` key
|
14
|
-
#
|
15
|
-
# @example
|
16
|
-
# case ui.ask('Do you like the NattyUI gem?')
|
17
|
-
# when true
|
18
|
-
# ui.info('Yeah!!')
|
19
|
-
# when false
|
20
|
-
# ui.write("That's pity!")
|
21
|
-
# else
|
22
|
-
# ui.failed('You should have an opinion!')
|
23
|
-
# end
|
24
|
-
#
|
25
|
-
# @see NattyUI.in_stream
|
26
|
-
#
|
27
|
-
# @param question [#to_s] Question to display
|
28
|
-
# @param yes [#to_s] chars which will be used to answer 'Yes'
|
29
|
-
# @param no [#to_s] chars which will be used to answer 'No'
|
30
|
-
# @return [Boolean] whether the answer is yes or no
|
31
|
-
# @return [nil] when input was aborted with `^C` or `^D`
|
32
|
-
def ask(question, yes: "jotsyd\r\n", no: 'n')
|
33
|
-
_element(:Ask, question, yes, no)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class Wrapper
|
38
|
-
#
|
39
|
-
# An {Element} to ask user input for yes/no queries.
|
40
|
-
#
|
41
|
-
# @see Features#ask
|
42
|
-
class Ask < Element
|
43
|
-
protected
|
44
|
-
|
45
|
-
def call(question, yes, no)
|
46
|
-
yes, no = grab(yes, no)
|
47
|
-
draw(question)
|
48
|
-
while true
|
49
|
-
char = NattyUI.read_key(mode: :raw)
|
50
|
-
return if "\3\4".include?(char)
|
51
|
-
return true if yes.include?(char)
|
52
|
-
return false if no.include?(char)
|
53
|
-
end
|
54
|
-
ensure
|
55
|
-
wrapper.ansi? ? (wrapper.stream << Ansi::CLL).flush : @parent.puts
|
56
|
-
end
|
57
|
-
|
58
|
-
def draw(question)
|
59
|
-
glyph = NattyUI::Glyph[:query]
|
60
|
-
@parent.print(
|
61
|
-
question,
|
62
|
-
prefix: "#{glyph} #{Ansi[255]}",
|
63
|
-
prefix_width: Text.width(glyph) + 1,
|
64
|
-
suffix_width: 0
|
65
|
-
)
|
66
|
-
end
|
67
|
-
|
68
|
-
def grab(yes, no)
|
69
|
-
yes = yes.to_s.chars.uniq
|
70
|
-
raise(ArgumentError, ':yes can not be empty') if yes.empty?
|
71
|
-
no = no.to_s.chars.uniq
|
72
|
-
raise(ArgumentError, ':no can not be empty') if no.empty?
|
73
|
-
return yes, no if (yes & no).empty?
|
74
|
-
raise(ArgumentError, 'chars in :yes and :no can not be intersect')
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
@@ -1,79 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'features'
|
4
|
-
|
5
|
-
module NattyUI
|
6
|
-
class Wrapper
|
7
|
-
#
|
8
|
-
# Basic visual element.
|
9
|
-
#
|
10
|
-
class Element
|
11
|
-
include Features
|
12
|
-
|
13
|
-
# @attribute [r] available_width
|
14
|
-
# @return [Integer] available columns count within the element
|
15
|
-
def available_width = @parent.available_width
|
16
|
-
|
17
|
-
# @attribute [r] closed?
|
18
|
-
# @return [Boolean] whether its closed or not
|
19
|
-
def closed? = (@status != nil)
|
20
|
-
|
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
|
35
|
-
|
36
|
-
# Close the element.
|
37
|
-
#
|
38
|
-
# @return [Element] itself
|
39
|
-
def close = _close(:closed)
|
40
|
-
|
41
|
-
alias _to_s to_s
|
42
|
-
private :_to_s
|
43
|
-
# @!visibility private
|
44
|
-
def inspect = _to_s
|
45
|
-
|
46
|
-
protected
|
47
|
-
|
48
|
-
def _close(state)
|
49
|
-
return self if @status
|
50
|
-
@status = state
|
51
|
-
finish
|
52
|
-
@raise ? raise(BREAK) : self
|
53
|
-
end
|
54
|
-
|
55
|
-
def call
|
56
|
-
NattyUI.instance_variable_set(:@element, self)
|
57
|
-
@raise = true
|
58
|
-
yield(self)
|
59
|
-
closed? ? self : close
|
60
|
-
rescue BREAK
|
61
|
-
nil
|
62
|
-
ensure
|
63
|
-
NattyUI.instance_variable_set(:@element, @parent)
|
64
|
-
end
|
65
|
-
|
66
|
-
def finish = nil
|
67
|
-
def prefix = "#{@parent.instance_variable_get(:@prefix)}#{@prefix}"
|
68
|
-
|
69
|
-
def initialize(parent)
|
70
|
-
@parent = parent
|
71
|
-
end
|
72
|
-
|
73
|
-
private_class_method :new
|
74
|
-
|
75
|
-
BREAK = Class.new(StandardError)
|
76
|
-
private_constant :BREAK
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module NattyUI
|
4
|
-
#
|
5
|
-
# Features of {NattyUI} - methods to display natty elements.
|
6
|
-
#
|
7
|
-
module Features
|
8
|
-
protected
|
9
|
-
|
10
|
-
def _element(type, ...)
|
11
|
-
wrapper.class.const_get(type).__send__(:new, self).__send__(:call, ...)
|
12
|
-
end
|
13
|
-
|
14
|
-
def _section(type, args = nil, owner: nil, **opts, &block)
|
15
|
-
sec = wrapper.class.const_get(type).__send__(:new, owner || self, **opts)
|
16
|
-
sec.puts(*args) if args && !args.empty?
|
17
|
-
sec.__send__(:call, &block) if block
|
18
|
-
sec
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'section'
|
4
|
-
|
5
|
-
module NattyUI
|
6
|
-
module Features
|
7
|
-
# Creates frame-enclosed section with a highlighted `title` and
|
8
|
-
# prints given additional arguments as lines into the section.
|
9
|
-
#
|
10
|
-
# When no block is given, the section must be closed, see
|
11
|
-
# {Wrapper::Element#close}.
|
12
|
-
#
|
13
|
-
# @param [Array<#to_s>] args more objects to print
|
14
|
-
# @param [Symbol, String] type frame type; see {NattyUI::Frame}
|
15
|
-
# @yieldparam [Wrapper::Framed] framed the created section
|
16
|
-
# @return [Object] the result of the code block
|
17
|
-
# @return [Wrapper::Framed] itself, when no code block is given
|
18
|
-
def framed(*args, type: :default, &block)
|
19
|
-
_section(:Framed, args, type: NattyUI::Frame[type], &block)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class Wrapper
|
24
|
-
#
|
25
|
-
# A frame-enclosed {Section} with a highlighted title.
|
26
|
-
#
|
27
|
-
# @see Features#framed
|
28
|
-
class Framed < Section
|
29
|
-
protected
|
30
|
-
|
31
|
-
def initialize(parent, type:)
|
32
|
-
super(parent, prefix: "#{type[4]} ", prefix_width: 2, suffix_width: 2)
|
33
|
-
init(type)
|
34
|
-
end
|
35
|
-
|
36
|
-
def init(type)
|
37
|
-
aw = @parent.available_width - 1
|
38
|
-
parent.puts("#{type[0]}#{type[5] * aw}")
|
39
|
-
@finish = "#{type[2]}#{type[5] * aw}"
|
40
|
-
end
|
41
|
-
|
42
|
-
def finish = @parent.puts(@finish)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'element'
|
4
|
-
|
5
|
-
module NattyUI
|
6
|
-
module Features
|
7
|
-
# Prints a H1 title.
|
8
|
-
#
|
9
|
-
# @param [#to_s] title text
|
10
|
-
# @return [Wrapper::Section, Wrapper] it's parent object
|
11
|
-
def h1(title) = _element(:Heading, title, '═══════')
|
12
|
-
|
13
|
-
# Prints a H2 title.
|
14
|
-
#
|
15
|
-
# @param (see #h1)
|
16
|
-
# @return (see #h1)
|
17
|
-
def h2(title) = _element(:Heading, title, '━━━━━')
|
18
|
-
|
19
|
-
# Prints a H3 title.
|
20
|
-
#
|
21
|
-
# @param (see #h1)
|
22
|
-
# @return (see #h1)
|
23
|
-
def h3(title) = _element(:Heading, title, '━━━')
|
24
|
-
|
25
|
-
# Prints a H4 title.
|
26
|
-
#
|
27
|
-
# @param (see #h1)
|
28
|
-
# @return (see #h1)
|
29
|
-
def h4(title) = _element(:Heading, title, '───')
|
30
|
-
|
31
|
-
# Prints a H5 title.
|
32
|
-
#
|
33
|
-
# @param (see #h1)
|
34
|
-
# @return (see #h1)
|
35
|
-
def h5(title) = _element(:Heading, title, '──')
|
36
|
-
end
|
37
|
-
|
38
|
-
class Wrapper
|
39
|
-
#
|
40
|
-
# A {Element} drawing a title.
|
41
|
-
#
|
42
|
-
# @see Features#h1
|
43
|
-
# @see Features#h2
|
44
|
-
# @see Features#h3
|
45
|
-
# @see Features#h4
|
46
|
-
# @see Features#h5
|
47
|
-
class Heading < Element
|
48
|
-
protected
|
49
|
-
|
50
|
-
def call(title, enclose)
|
51
|
-
@parent.puts(
|
52
|
-
title,
|
53
|
-
prefix: "#{Ansi[39]}#{enclose} #{Ansi[:bold, 255]}",
|
54
|
-
suffix: " #{Ansi[:bold_off, 39]}#{enclose}#{Ansi::RESET}",
|
55
|
-
max_width: available_width - 2 - (enclose.size * 2)
|
56
|
-
)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'element'
|
4
|
-
|
5
|
-
module NattyUI
|
6
|
-
module Features
|
7
|
-
# Print a horizontal rule
|
8
|
-
#
|
9
|
-
# @param [#to_s] symbol string to build the horizontal rule
|
10
|
-
# @return [Wrapper::Section, Wrapper] it's parent object
|
11
|
-
def hr(symbol = '─') = _element(:HorizontalRule, symbol)
|
12
|
-
end
|
13
|
-
|
14
|
-
class Wrapper
|
15
|
-
#
|
16
|
-
# A {Element} drawing a horizontal rule.
|
17
|
-
#
|
18
|
-
# @see Features#hr
|
19
|
-
class HorizontalRule < Element
|
20
|
-
protected
|
21
|
-
|
22
|
-
def call(symbol)
|
23
|
-
size = Text.width(symbol = symbol.to_s)
|
24
|
-
return @parent.puts if size == 0
|
25
|
-
max_width = available_width
|
26
|
-
@parent.print(
|
27
|
-
symbol * ((max_width / size)),
|
28
|
-
max_width: max_width,
|
29
|
-
prefix: Ansi[39],
|
30
|
-
prefix_width: 0,
|
31
|
-
suffix: Ansi::RESET,
|
32
|
-
suffix_width: 0
|
33
|
-
)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,138 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'element'
|
4
|
-
|
5
|
-
module NattyUI
|
6
|
-
module Features
|
7
|
-
#
|
8
|
-
# Print items of a given list as columns.
|
9
|
-
# In the default compact format columns may have diffrent widths and the
|
10
|
-
# list items are ordered column-wise.
|
11
|
-
# The non-compact format prints all columns in same width and order the list
|
12
|
-
# items row-wise.
|
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
|
-
#
|
38
|
-
# @param [Array<#to_s>] args items to print
|
39
|
-
# @param [Boolean] compact whether to use compact format
|
40
|
-
# @param [nil,#to_s,Integer,Symbol] glyph optional glyph used as element
|
41
|
-
# prefix
|
42
|
-
# @return [Wrapper, Wrapper::Element] itself
|
43
|
-
def ls(*args, compact: true, glyph: nil)
|
44
|
-
_element(:ListInColumns, args, compact, glyph)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
class Wrapper
|
49
|
-
#
|
50
|
-
# An {Element} to print items of a given list as columns.
|
51
|
-
#
|
52
|
-
# @see Features#ls
|
53
|
-
class ListInColumns < Element
|
54
|
-
protected
|
55
|
-
|
56
|
-
def call(list, compact, glyph)
|
57
|
-
return @parent if list.empty?
|
58
|
-
list.flatten!
|
59
|
-
cvt = cvt(glyph, list.size)
|
60
|
-
list.map! { |item| Item.new(item = cvt[item], Text.width(item)) }
|
61
|
-
if compact
|
62
|
-
each_compacted(list, available_width - 1) { @parent.puts(_1) }
|
63
|
-
else
|
64
|
-
each(list, available_width - 1) { @parent.puts(_1) }
|
65
|
-
end
|
66
|
-
@parent
|
67
|
-
end
|
68
|
-
|
69
|
-
def cvt(glyph, size)
|
70
|
-
case glyph
|
71
|
-
when nil, false
|
72
|
-
->(s) { Text.embellish(s) }
|
73
|
-
when :hex
|
74
|
-
pad = size.to_s(16).size
|
75
|
-
glyph = 0
|
76
|
-
lambda do |s|
|
77
|
-
"#{(glyph += 1).to_s(16).rjust(pad, '0')} #{Text.embellish(s)}"
|
78
|
-
end
|
79
|
-
when Integer
|
80
|
-
pad = (glyph + size).to_s.size
|
81
|
-
glyph -= 1
|
82
|
-
->(s) { "#{(glyph += 1).to_s.rjust(pad)} #{Text.embellish(s)}" }
|
83
|
-
when Symbol
|
84
|
-
lambda do |s|
|
85
|
-
"#{
|
86
|
-
t = glyph
|
87
|
-
glyph = glyph.succ
|
88
|
-
t
|
89
|
-
} #{Text.embellish(s)}"
|
90
|
-
end
|
91
|
-
else
|
92
|
-
->(s) { "#{Text.embellish(glyph)} #{Text.embellish(s)}" }
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def each(list, max_width)
|
97
|
-
width = list.max_by(&:width).width + 3
|
98
|
-
list.each_slice(max_width / width) do |slice|
|
99
|
-
yield(slice.map { _1.to_s(width) }.join)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def each_compacted(list, max_width)
|
104
|
-
found, widths = find_columns(list, max_width)
|
105
|
-
fill(found[-1], found[0].size)
|
106
|
-
found.transpose.each do |row|
|
107
|
-
row = row.each_with_index.map { |item, col| item&.to_s(widths[col]) }
|
108
|
-
yield(row.join)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def find_columns(list, max_width)
|
113
|
-
found = [list]
|
114
|
-
widths = [list.max_by(&:width).width]
|
115
|
-
1.upto(list.size - 1) do |slice_size|
|
116
|
-
candidate = list.each_slice(list.size / slice_size).to_a
|
117
|
-
cwidths = candidate.map { _1.max_by(&:width).width + 3 }
|
118
|
-
cwidths[-1] -= 3
|
119
|
-
break if cwidths.sum > max_width
|
120
|
-
found = candidate
|
121
|
-
widths = cwidths
|
122
|
-
end
|
123
|
-
[found, widths]
|
124
|
-
end
|
125
|
-
|
126
|
-
def fill(ary, size)
|
127
|
-
(diff = size - ary.size).positive? && ary.fill(nil, ary.size, diff)
|
128
|
-
end
|
129
|
-
|
130
|
-
Item =
|
131
|
-
Struct.new(:str, :width) do
|
132
|
-
def to_s(in_width) = "#{str}#{' ' * (in_width - width)}"
|
133
|
-
end
|
134
|
-
|
135
|
-
private_constant :Item
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|