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.
@@ -3,54 +3,62 @@
3
3
  require_relative 'utils'
4
4
 
5
5
  module NattyUI
6
- module HBarsRenderer
7
- class << self
8
- def lines(vals, width, normalize, style, text_style)
9
- if text_style
10
- bots = Ansi[*text_style]
11
- eots = Ansi::RESET
12
- end
13
- texts = vals.map { Str.new(_1) }
14
- tw = texts.max_by(&:width).width
15
- size = width - tw - 1
16
- if size < 2 # text only
17
- return texts.map { "#{bots}#{' ' * (tw - _1.width)}#{_1}#{eots}" }
18
- end
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
- def lines_bars_only(vals, width, normalize, style)
32
- if style
33
- bos = Ansi[*style]
34
- eos = Ansi::RESET
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
- private
32
+ private
42
33
 
43
- def adjust!(vals, size)
44
- max = vals.max.to_f
45
- vals.map { ((_1 / max) * size).round }
46
- end
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
- def normalize!(vals, size)
49
- min, max = vals.minmax
50
- return Array.new(vals.size, size) if min == max
51
- max = (max - min).to_f
52
- vals.map { (((_1 - min) / max) * size).round }
53
- end
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
 
@@ -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
- @flp = "#{cm} #{@style}"
104
- @flpw = cm.width + 1
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
@@ -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::Base
13
- include NattyUI::Attributes::Width
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::Base
173
- prepend NattyUI::Attributes::Width
174
- prepend NattyUI::Attributes::Padding
175
- prepend NattyUI::Attributes::Align
176
- prepend NattyUI::Attributes::Vertical
177
- prepend NattyUI::Attributes::Style
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::Base
182
- prepend NattyUI::Attributes::Border
183
- prepend NattyUI::Attributes::BorderStyle
184
- prepend NattyUI::Attributes::Position
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)