natty-ui 0.30.0 → 0.31.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/README.md +2 -1
- data/examples/cols.rb +4 -6
- data/examples/hbars.rb +2 -3
- data/examples/illustration.rb +13 -14
- data/examples/info.rb +9 -13
- data/examples/named-colors.rb +3 -5
- data/examples/tables.rb +5 -8
- data/examples/tasks.rb +14 -14
- data/examples/themes.rb +51 -0
- data/lib/natty-ui/attributes.rb +37 -38
- data/lib/natty-ui/features.rb +58 -31
- data/lib/natty-ui/hbars_renderer.rb +51 -43
- data/lib/natty-ui/progress.rb +5 -7
- data/lib/natty-ui/shell_command.rb +132 -0
- data/lib/natty-ui/table.rb +47 -12
- data/lib/natty-ui/table_renderer.rb +3 -2
- data/lib/natty-ui/theme.rb +326 -282
- data/lib/natty-ui/utils.rb +3 -3
- data/lib/natty-ui/vbars_renderer.rb +1 -0
- data/lib/natty-ui/version.rb +1 -1
- data/lib/natty-ui.rb +3 -1
- metadata +4 -2
@@ -3,54 +3,62 @@
|
|
3
3
|
require_relative 'utils'
|
4
4
|
|
5
5
|
module NattyUI
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
if style
|
20
|
-
bos = Ansi[*style]
|
21
|
-
eos = Ansi::RESET
|
22
|
-
end
|
23
|
-
vals = normalize ? normalize!(vals, size) : adjust!(vals, size)
|
24
|
-
texts.map.with_index do |str, idx|
|
25
|
-
"#{bots}#{' ' * (tw - str.width)}#{str}#{eots}#{bos}▕#{
|
26
|
-
'▆' * vals[idx]
|
27
|
-
}#{eos}"
|
28
|
-
end
|
29
|
-
end
|
6
|
+
class HBarsRenderer
|
7
|
+
def initialize(values, min, max)
|
8
|
+
@values = values
|
9
|
+
@min = min
|
10
|
+
@max = max
|
11
|
+
end
|
12
|
+
|
13
|
+
def with_text(style)
|
14
|
+
@texts = @values.map { Str.new(_1) }
|
15
|
+
@texts_width = @texts.max_by(&:width).width
|
16
|
+
@texts.map!(&format_text(style, @texts_width))
|
17
|
+
self
|
18
|
+
end
|
30
19
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
36
|
-
width -= 1
|
37
|
-
vals = normalize ? normalize!(vals, width) : adjust!(vals, width)
|
38
|
-
vals.map { "#{bos}▕#{'▆' * _1}#{eos}" }
|
20
|
+
def lines(width, style, normalize)
|
21
|
+
if @texts
|
22
|
+
width -= @texts_width
|
23
|
+
return @texts if width < 3
|
39
24
|
end
|
25
|
+
width -= 1
|
26
|
+
fmt = format(style)
|
27
|
+
vals = normalize ? normalized(width, &fmt) : adjusted(width, &fmt)
|
28
|
+
vals = @texts.zip(vals).map!(&:join) if @texts
|
29
|
+
vals
|
30
|
+
end
|
40
31
|
|
41
|
-
|
32
|
+
private
|
42
33
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
34
|
+
def format_text(style, width)
|
35
|
+
return ->(l) { "#{' ' * (width - l.width)}#{l}" } unless style
|
36
|
+
bot = Ansi[*style]
|
37
|
+
eot = Ansi::RESET
|
38
|
+
->(l) { "#{bot}#{' ' * (width - l.width)}#{l}#{eot}" }
|
39
|
+
end
|
47
40
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
41
|
+
def format(style)
|
42
|
+
return ->(l) { "▕#{'▆' * l}" } unless style
|
43
|
+
bos = Ansi[*style]
|
44
|
+
eos = Ansi::RESET
|
45
|
+
->(l) { "#{bos}▕#{'▆' * l}#{eos}" }
|
46
|
+
end
|
47
|
+
|
48
|
+
def adjusted(size)
|
49
|
+
vals = @values.map(&:to_f)
|
50
|
+
max = vals.max
|
51
|
+
max = @max.to_f if @max&.>(max)
|
52
|
+
vals.map! { yield(((_1 / max) * size).round) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def normalized(size)
|
56
|
+
vals = @values.map(&:to_f)
|
57
|
+
min, max = vals.minmax
|
58
|
+
min = @min.to_f if @min&.<(min)
|
59
|
+
max = @max.to_f if @max&.>(max)
|
60
|
+
max -= min
|
61
|
+
vals.map! { yield((((_1 - min) / max) * size).round) }
|
54
62
|
end
|
55
63
|
end
|
56
64
|
|
data/lib/natty-ui/progress.rb
CHANGED
@@ -100,8 +100,10 @@ module NattyUI
|
|
100
100
|
@pin_line = NattyUI.lines_written
|
101
101
|
@style = Theme.current.task_style
|
102
102
|
cm = Theme.current.mark(:current)
|
103
|
-
@
|
104
|
-
|
103
|
+
@redraw_opts = {
|
104
|
+
first_line_prefix: "#{cm} #{@style}",
|
105
|
+
first_line_prefix_width: cm.width + 1
|
106
|
+
}
|
105
107
|
max ? self.max = max : redraw
|
106
108
|
end
|
107
109
|
|
@@ -111,11 +113,7 @@ module NattyUI
|
|
111
113
|
curr = bar ? [@title, bar] : [@title]
|
112
114
|
return if @last == curr
|
113
115
|
@pin_line = NattyUI.back_to_line(@pin_line) if @last
|
114
|
-
@parent.puts(
|
115
|
-
*curr,
|
116
|
-
first_line_prefix: @flp,
|
117
|
-
first_line_prefix_width: @flpw
|
118
|
-
)
|
116
|
+
@parent.puts(*curr, **@redraw_opts)
|
119
117
|
@last = curr
|
120
118
|
end
|
121
119
|
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module NattyUI
|
2
|
+
module ShellCommand
|
3
|
+
class << self
|
4
|
+
def call(*cmd, shell: false, input: nil, **options)
|
5
|
+
options = options.except(:in, :out, :err)
|
6
|
+
env = (cmd[0].is_a?(Hash) ? cmd.shift.dup : {}).freeze
|
7
|
+
if shell
|
8
|
+
cmd = cmd.map! { _escape(it) }.join(' ')
|
9
|
+
elsif cmd.size == 1 && cmd[0].include?(' ')
|
10
|
+
cmd = cmd[0]
|
11
|
+
end
|
12
|
+
cmd.freeze
|
13
|
+
input = Input.for(input)
|
14
|
+
ret = nil
|
15
|
+
with_io(options, input) do |cio, out_r, err_r, in_w|
|
16
|
+
thread = Process.detach(Process.spawn(env, *cmd, options))
|
17
|
+
begin
|
18
|
+
cio.each(&:close)
|
19
|
+
read = [out_r, err_r]
|
20
|
+
write = [in_w] if in_w
|
21
|
+
while !read.empty? || write
|
22
|
+
rr, wr, = IO.select(read, write)
|
23
|
+
if rr.include?(out_r)
|
24
|
+
begin
|
25
|
+
yield(out_r.readline(chomp: true), :output)
|
26
|
+
rescue SystemCallError, IOError
|
27
|
+
read.delete(out_r)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
if rr.include?(err_r)
|
31
|
+
begin
|
32
|
+
yield(err_r.readline(chomp: true), :error)
|
33
|
+
rescue SystemCallError, IOError
|
34
|
+
read.delete(err_r)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
next if wr.empty?
|
38
|
+
begin
|
39
|
+
next if input.call(in_w)
|
40
|
+
rescue SystemCallError, IOError
|
41
|
+
# nop
|
42
|
+
end
|
43
|
+
in_w.close
|
44
|
+
write = nil
|
45
|
+
end
|
46
|
+
ensure
|
47
|
+
ret = thread.join.value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
ret
|
51
|
+
rescue SystemCallError, IOError
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def with_io(options, input)
|
58
|
+
IO.pipe do |out_r, out_w|
|
59
|
+
IO.pipe do |err_r, err_w|
|
60
|
+
cio = [options[:out] = out_w, options[:err] = err_w]
|
61
|
+
return yield(cio, out_r, err_r) unless input
|
62
|
+
IO.pipe do |in_r, in_w|
|
63
|
+
cio << (options[:in] = in_r)
|
64
|
+
in_w.sync = true
|
65
|
+
yield(cio, out_r, err_r, in_w)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def _escape(str)
|
72
|
+
return +"''" if str.empty?
|
73
|
+
str = str.dup
|
74
|
+
str.gsub!(%r{[^A-Za-z0-9_\-.,:+/@\n]}, "\\\\\\&")
|
75
|
+
str.gsub!("\n", "'\n'")
|
76
|
+
str
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module Input
|
81
|
+
def self.for(obj)
|
82
|
+
return unless obj
|
83
|
+
return CopyWriter.new(obj) if obj.respond_to?(:readpartial)
|
84
|
+
return CopyWriter.new(obj.to_io) if obj.respond_to?(:to_io)
|
85
|
+
return ArrayWriter.new(obj) if obj.is_a?(Array)
|
86
|
+
return EnumerableWriter.new(obj) if obj.respond_to?(:each)
|
87
|
+
return ArrayWriter.new(obj.to_a) if obj.respond_to?(:to_a)
|
88
|
+
Writer.new(obj)
|
89
|
+
end
|
90
|
+
|
91
|
+
class Writer
|
92
|
+
def call(io) = (io.write(_next) if @obj)
|
93
|
+
def initialize(obj) = (@obj = obj)
|
94
|
+
def _next = (_, @obj = @obj, nil).first
|
95
|
+
end
|
96
|
+
|
97
|
+
class CopyWriter < Writer
|
98
|
+
def call(io) = (IO.copy_stream(_next, io) if @obj)
|
99
|
+
end
|
100
|
+
|
101
|
+
class ArrayWriter
|
102
|
+
def call(io) = io.write(@ry[@idx += 1] || return)
|
103
|
+
|
104
|
+
def initialize(ary)
|
105
|
+
@ry = ary
|
106
|
+
@idx = -1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class EnumerableWriter
|
111
|
+
def call(io)
|
112
|
+
io.write(@enum.next)
|
113
|
+
rescue StopIteration
|
114
|
+
false
|
115
|
+
end
|
116
|
+
|
117
|
+
def initialize(enum)
|
118
|
+
@enum =
|
119
|
+
if enum.respond_to?(:enum_for)
|
120
|
+
enum.enum_for(:each)
|
121
|
+
else
|
122
|
+
Enumerator.new { |y| enum.each { y << it } }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private_constant :Input
|
129
|
+
end
|
130
|
+
|
131
|
+
private_constant :ShellCommand
|
132
|
+
end
|
data/lib/natty-ui/table.rb
CHANGED
@@ -9,8 +9,8 @@ module NattyUI
|
|
9
9
|
# Collection of rows and columns used by {Features.table}.
|
10
10
|
#
|
11
11
|
class Table
|
12
|
-
class Column < NattyUI::Attributes
|
13
|
-
include
|
12
|
+
class Column < NattyUI::Attributes
|
13
|
+
include Width
|
14
14
|
|
15
15
|
# @return [Integer] column index
|
16
16
|
attr_reader :index
|
@@ -22,6 +22,12 @@ module NattyUI
|
|
22
22
|
def to_s = "#{super.chop} @index:#{@index} @width:#{width.inspect}>"
|
23
23
|
alias inspect to_s
|
24
24
|
|
25
|
+
def assign(**attributes)
|
26
|
+
return self if attributes.empty?
|
27
|
+
@parent.each_cell_of(@index) { _1.attributes.merge!(**attributes) }
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
25
31
|
private
|
26
32
|
|
27
33
|
def find_width
|
@@ -137,6 +143,12 @@ module NattyUI
|
|
137
143
|
self
|
138
144
|
end
|
139
145
|
|
146
|
+
def assign(**attributes)
|
147
|
+
return self if attributes.empty?
|
148
|
+
@cells.each { _1.attributes.merge!(**attributes) }
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
140
152
|
private
|
141
153
|
|
142
154
|
def respond_to_missing?(name, _)
|
@@ -169,19 +181,42 @@ module NattyUI
|
|
169
181
|
class Cell
|
170
182
|
include TextWithAttributes
|
171
183
|
|
172
|
-
class Attributes < NattyUI::Attributes
|
173
|
-
prepend
|
174
|
-
prepend
|
175
|
-
prepend
|
176
|
-
prepend
|
177
|
-
prepend
|
184
|
+
class Attributes < NattyUI::Attributes
|
185
|
+
prepend Width
|
186
|
+
prepend Padding
|
187
|
+
prepend Align
|
188
|
+
prepend Vertical
|
189
|
+
prepend Style
|
190
|
+
|
191
|
+
# Whether the text's line breaks are processed.
|
192
|
+
#
|
193
|
+
# @return [true, false]
|
194
|
+
attr_reader :eol
|
195
|
+
|
196
|
+
# @attribute [w] eol
|
197
|
+
def eol=(value)
|
198
|
+
@eol = value ? true : false
|
199
|
+
end
|
200
|
+
|
201
|
+
protected
|
202
|
+
|
203
|
+
def _assign(opt)
|
204
|
+
@eol = opt[:eol]
|
205
|
+
@eol = true if @eol.nil?
|
206
|
+
super
|
207
|
+
end
|
208
|
+
|
209
|
+
def _store(opt)
|
210
|
+
opt[:eol] = false unless @eol
|
211
|
+
super
|
212
|
+
end
|
178
213
|
end
|
179
214
|
end
|
180
215
|
|
181
|
-
class Attributes < NattyUI::Attributes
|
182
|
-
prepend
|
183
|
-
prepend
|
184
|
-
prepend
|
216
|
+
class Attributes < NattyUI::Attributes
|
217
|
+
prepend Border
|
218
|
+
prepend BorderStyle
|
219
|
+
prepend Position
|
185
220
|
|
186
221
|
# Whether the table has a border around.
|
187
222
|
#
|
@@ -115,12 +115,12 @@ module NattyUI
|
|
115
115
|
@align = att.align
|
116
116
|
@vertical = att.vertical
|
117
117
|
@style = att.style_bbcode
|
118
|
-
@text = width_corrected(cell.text, width)
|
118
|
+
@text = width_corrected(cell.text, width, att.eol == false)
|
119
119
|
end
|
120
120
|
|
121
121
|
private
|
122
122
|
|
123
|
-
def width_corrected(text, width)
|
123
|
+
def width_corrected(text, width, ignore_newline)
|
124
124
|
@width, @padding[3], @padding[1] =
|
125
125
|
WidthFinder.adjust(width, @padding[3], @padding[1])
|
126
126
|
@empty = @style ? "#{@style}#{' ' * width}[/]" : ' ' * width
|
@@ -130,6 +130,7 @@ module NattyUI
|
|
130
130
|
*text,
|
131
131
|
limit: @width,
|
132
132
|
bbcode: true,
|
133
|
+
ignore_newline: ignore_newline,
|
133
134
|
ansi: Terminal.ansi?
|
134
135
|
).map(&txt_fmt),
|
135
136
|
Array.new(@padding[2], @empty)
|