natty-ui 0.35.0 → 1.0.2
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 +6 -6
- data/examples/24bit-colors.rb +9 -5
- data/examples/3bit-colors.rb +7 -7
- data/examples/8bit-colors.rb +5 -7
- data/examples/attributes.rb +2 -3
- data/examples/elements.rb +9 -6
- data/examples/examples.rb +9 -9
- data/examples/frames.rb +31 -0
- data/examples/hbars.rb +6 -3
- data/examples/info.rb +13 -10
- data/examples/key-codes.rb +8 -9
- data/examples/ls.rb +24 -22
- data/examples/named-colors.rb +4 -3
- data/examples/sections.rb +26 -24
- data/examples/select.rb +28 -0
- data/examples/sh.rb +25 -7
- data/examples/tables.rb +19 -37
- data/examples/tasks.rb +32 -22
- data/examples/vbars.rb +5 -3
- data/lib/natty-ui/dumb_progress.rb +68 -0
- data/lib/natty-ui/element.rb +61 -70
- data/lib/natty-ui/features.rb +771 -949
- data/lib/natty-ui/frame.rb +87 -0
- data/lib/natty-ui/helper/table.rb +1376 -0
- data/lib/natty-ui/margin.rb +83 -0
- data/lib/natty-ui/progress.rb +116 -152
- data/lib/natty-ui/renderer/bars.rb +93 -0
- data/lib/natty-ui/renderer/choice.rb +56 -0
- data/lib/natty-ui/renderer/dumb_choice.rb +34 -0
- data/lib/natty-ui/renderer/dumb_select.rb +60 -0
- data/lib/natty-ui/renderer/dumb_shell_runner.rb +19 -0
- data/lib/natty-ui/renderer/heading.rb +26 -0
- data/lib/natty-ui/renderer/horizontal_rule.rb +32 -0
- data/lib/natty-ui/{ls_renderer.rb → renderer/ls.rb} +15 -27
- data/lib/natty-ui/renderer/mark.rb +13 -0
- data/lib/natty-ui/renderer/quote.rb +13 -0
- data/lib/natty-ui/renderer/select.rb +63 -0
- data/lib/natty-ui/renderer/shell.rb +15 -0
- data/lib/natty-ui/renderer/shell_runner.rb +29 -0
- data/lib/natty-ui/renderer/table_renderer.rb +429 -0
- data/lib/natty-ui/section.rb +144 -32
- data/lib/natty-ui/task.rb +38 -25
- data/lib/natty-ui/temporary.rb +27 -14
- data/lib/natty-ui/utils/border.rb +139 -0
- data/lib/natty-ui/utils/str_const.rb +62 -0
- data/lib/natty-ui/utils/utils.rb +47 -0
- data/lib/natty-ui/version.rb +1 -1
- data/lib/natty-ui.rb +76 -35
- metadata +31 -28
- data/examples/cols.rb +0 -38
- data/examples/illustration.rb +0 -60
- data/examples/options.rb +0 -28
- data/examples/themes.rb +0 -51
- data/lib/natty-ui/attributes.rb +0 -593
- data/lib/natty-ui/choice.rb +0 -67
- data/lib/natty-ui/dumb_choice.rb +0 -47
- data/lib/natty-ui/dumb_options.rb +0 -64
- data/lib/natty-ui/framed.rb +0 -50
- data/lib/natty-ui/hbars_renderer.rb +0 -66
- data/lib/natty-ui/options.rb +0 -78
- data/lib/natty-ui/shell_renderer.rb +0 -91
- data/lib/natty-ui/table.rb +0 -325
- data/lib/natty-ui/table_renderer.rb +0 -165
- data/lib/natty-ui/theme.rb +0 -403
- data/lib/natty-ui/utils.rb +0 -111
- data/lib/natty-ui/vbars_renderer.rb +0 -49
- data/lib/natty-ui/width_finder.rb +0 -137
- data/natty-ui.gemspec +0 -34
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'element'
|
|
4
|
-
|
|
5
|
-
module NattyUI
|
|
6
|
-
class DumbOptions < Element
|
|
7
|
-
def select
|
|
8
|
-
yield(self) if block_given?
|
|
9
|
-
draw
|
|
10
|
-
Terminal.on_key_event do |event|
|
|
11
|
-
return if @abortable && %w[Esc Ctrl+c].include?(event.name)
|
|
12
|
-
return @opts.transform_values(&:last) if event.name == 'Enter'
|
|
13
|
-
next true unless event.simple?
|
|
14
|
-
code = event.raw.upcase
|
|
15
|
-
if @opts.size <= 9
|
|
16
|
-
next true unless ('1'..'9').include?(code)
|
|
17
|
-
offset = 49
|
|
18
|
-
elsif ('A'..'Z').include?(code)
|
|
19
|
-
offset = 65
|
|
20
|
-
else
|
|
21
|
-
next true
|
|
22
|
-
end
|
|
23
|
-
key = @opts.keys[code.ord - offset] or next true
|
|
24
|
-
@opts[key][-1] = !@opts[key][-1]
|
|
25
|
-
@parent.space
|
|
26
|
-
draw
|
|
27
|
-
end
|
|
28
|
-
ensure
|
|
29
|
-
@parent.space
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def initialize(parent, opts, abortable, selected)
|
|
35
|
-
super(parent)
|
|
36
|
-
@opts =
|
|
37
|
-
opts.to_h do |k, v|
|
|
38
|
-
v.is_a?(Enumerable) ? [k, [v[0], !!v[-1]]] : [k, [k, !!v]]
|
|
39
|
-
end
|
|
40
|
-
@abortable = abortable
|
|
41
|
-
@current = @opts.key?(selected) ? selected : @opts.first.first
|
|
42
|
-
theme = Theme.current
|
|
43
|
-
@marks = {
|
|
44
|
-
true => theme.mark(:checkmark),
|
|
45
|
-
false => theme.mark(:choice)
|
|
46
|
-
}.compare_by_identity
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def draw
|
|
50
|
-
glyph = @opts.size <= 9 ? 1 : 'A'
|
|
51
|
-
@opts.each_pair do |_, (str, selected)|
|
|
52
|
-
mark = @marks[selected]
|
|
53
|
-
@parent.puts(
|
|
54
|
-
str,
|
|
55
|
-
first_line_prefix: "[\\#{glyph}] #{mark}",
|
|
56
|
-
first_line_prefix_width: mark.width + 2
|
|
57
|
-
)
|
|
58
|
-
glyph = glyph.succ
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private_constant :DumbOptions
|
|
64
|
-
end
|
data/lib/natty-ui/framed.rb
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'element'
|
|
4
|
-
|
|
5
|
-
module NattyUI
|
|
6
|
-
# {Element} with a frame around the content used by {Features.framed}.
|
|
7
|
-
#
|
|
8
|
-
class Framed < Element
|
|
9
|
-
# @private
|
|
10
|
-
def closed? = @bottom ? false : true
|
|
11
|
-
|
|
12
|
-
# @private
|
|
13
|
-
def puts(*objects, **options)
|
|
14
|
-
return self if closed?
|
|
15
|
-
options[:align] = @align
|
|
16
|
-
options[:expand] = true
|
|
17
|
-
super
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# @private
|
|
21
|
-
def done
|
|
22
|
-
return if closed?
|
|
23
|
-
@parent.puts(@bottom)
|
|
24
|
-
@bottom = nil
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# @private
|
|
28
|
-
def inspect = "#{_to_s.chop} align=#{@align.inspect} closed?=#{closed?}>"
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def initialize(parent, align, chars, style)
|
|
33
|
-
super(parent)
|
|
34
|
-
@align = align
|
|
35
|
-
if style
|
|
36
|
-
style = Ansi[*style]
|
|
37
|
-
@prefix = "#{style}#{chars[9]}[/] "
|
|
38
|
-
@suffix = " #{style}#{chars[9]}[/]"
|
|
39
|
-
else
|
|
40
|
-
@prefix = "#{chars[9]} "
|
|
41
|
-
@suffix = " #{chars[9]}"
|
|
42
|
-
end
|
|
43
|
-
@prefix_width = 2
|
|
44
|
-
@suffix_width = 2
|
|
45
|
-
line = chars[10] * (parent.columns - 2)
|
|
46
|
-
parent.puts("#{style}#{chars[0]}#{line}#{chars[2]}")
|
|
47
|
-
@bottom = "#{style}#{chars[6]}#{line}#{chars[8]}"
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'utils'
|
|
4
|
-
|
|
5
|
-
module NattyUI
|
|
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
|
|
19
|
-
|
|
20
|
-
def lines(width, style, normalize)
|
|
21
|
-
if @texts
|
|
22
|
-
width -= @texts_width
|
|
23
|
-
return @texts if width < 3
|
|
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
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
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
|
|
40
|
-
|
|
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) }
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
private_constant :HBarsRenderer
|
|
66
|
-
end
|
data/lib/natty-ui/options.rb
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'element'
|
|
4
|
-
|
|
5
|
-
module NattyUI
|
|
6
|
-
class Options < Element
|
|
7
|
-
def select
|
|
8
|
-
yield(self) if block_given?
|
|
9
|
-
pin_line = NattyUI.lines_written
|
|
10
|
-
draw
|
|
11
|
-
last = @current
|
|
12
|
-
Terminal.on_key_event do |event|
|
|
13
|
-
case event.name
|
|
14
|
-
when 'Esc', 'Ctrl+c'
|
|
15
|
-
return if @abortable
|
|
16
|
-
when 'Enter'
|
|
17
|
-
return @opts.transform_values(&:last)
|
|
18
|
-
when 'Space'
|
|
19
|
-
(last = @opts[@current])[-1] = !last[-1]
|
|
20
|
-
when 'a'
|
|
21
|
-
@opts.transform_values { _1[-1] = true }
|
|
22
|
-
last = nil
|
|
23
|
-
when 'n'
|
|
24
|
-
@opts.transform_values { _1[-1] = false }
|
|
25
|
-
last = nil
|
|
26
|
-
when 'Home'
|
|
27
|
-
@current = @opts.first.first
|
|
28
|
-
when 'End'
|
|
29
|
-
@current = @opts.keys.last
|
|
30
|
-
when 'Up', 'Back', 'Shift+Tab', 'i'
|
|
31
|
-
keys = @opts.keys
|
|
32
|
-
@current = keys[keys.index(@current) - 1]
|
|
33
|
-
when 'Down', 'Tab', 'k'
|
|
34
|
-
keys = @opts.keys
|
|
35
|
-
@current = keys[keys.index(@current) + 1] || keys[0]
|
|
36
|
-
end
|
|
37
|
-
next true if last == @current
|
|
38
|
-
pin_line = NattyUI.back_to_line(pin_line, erase: false)
|
|
39
|
-
draw
|
|
40
|
-
last = @current
|
|
41
|
-
end
|
|
42
|
-
ensure
|
|
43
|
-
NattyUI.back_to_line(@start_line)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
def initialize(parent, opts, abortable, selected)
|
|
49
|
-
super(parent)
|
|
50
|
-
@opts =
|
|
51
|
-
opts.to_h do |k, v|
|
|
52
|
-
v.is_a?(Enumerable) ? [k, [v[0], !!v[-1]]] : [k, [k, !!v]]
|
|
53
|
-
end
|
|
54
|
-
@abortable = abortable
|
|
55
|
-
@current = @opts.key?(selected) ? selected : @opts.first.first
|
|
56
|
-
@start_line = NattyUI.lines_written
|
|
57
|
-
theme = Theme.current
|
|
58
|
-
@style = {
|
|
59
|
-
false => theme.choice_style,
|
|
60
|
-
true => theme.choice_current_style
|
|
61
|
-
}.compare_by_identity
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def draw
|
|
65
|
-
states = NattyUI::Theme.current.option_states
|
|
66
|
-
@opts.each_pair do |key, (str, selected)|
|
|
67
|
-
mark = states.dig(current = key == @current, selected)
|
|
68
|
-
@parent.puts(
|
|
69
|
-
"#{@style[current]}#{str}",
|
|
70
|
-
first_line_prefix: mark,
|
|
71
|
-
first_line_prefix_width: mark.width
|
|
72
|
-
)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
private_constant :Options
|
|
78
|
-
end
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'theme'
|
|
4
|
-
|
|
5
|
-
module NattyUI
|
|
6
|
-
module ShellRenderer
|
|
7
|
-
def self.sh(parent, cmd, options, space)
|
|
8
|
-
opts_out, opts_err = opts_from(Theme.current)
|
|
9
|
-
Terminal.sh(*cmd, **options) do |line, kind|
|
|
10
|
-
parent.puts(
|
|
11
|
-
space ? line.gsub("\t", ' ').gsub(/[[:space:]]/, ' ') : line,
|
|
12
|
-
**(kind == :error ? opts_err : opts_out)
|
|
13
|
-
)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
if Terminal.ansi?
|
|
18
|
-
def self.opts_from(theme)
|
|
19
|
-
out = theme.mark(:sh_out)
|
|
20
|
-
err = theme.mark(:sh_err)
|
|
21
|
-
[
|
|
22
|
-
{
|
|
23
|
-
bbcode: false,
|
|
24
|
-
prefix: "#{out}#{theme.sh_out_style}",
|
|
25
|
-
prefix_width: out.width
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
bbcode: false,
|
|
29
|
-
prefix: "#{err}#{theme.sh_err_style}",
|
|
30
|
-
prefix_width: err.width
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def self.run(parent, cmd, options, space, tail)
|
|
36
|
-
theme = Theme.current
|
|
37
|
-
opref = "#{theme.mark(:sh_out)}#{theme.sh_out_style}"
|
|
38
|
-
epref = "#{theme.mark(:sh_err)}#{theme.sh_err_style}"
|
|
39
|
-
out, err, show = [], [], []
|
|
40
|
-
start = NattyUI.lines_written
|
|
41
|
-
[
|
|
42
|
-
Terminal.sh(*cmd, **options) do |line, kind|
|
|
43
|
-
start = NattyUI.back_to_line(start)
|
|
44
|
-
ol = space ? line.gsub("\t", ' ').gsub(/[[:space:]]/, ' ') : line
|
|
45
|
-
if kind == :error
|
|
46
|
-
err << line
|
|
47
|
-
show << "#{epref}#{ol}"
|
|
48
|
-
else
|
|
49
|
-
out << line
|
|
50
|
-
show << "#{opref}#{ol}"
|
|
51
|
-
end
|
|
52
|
-
parent.puts(*show, bbcode: false, tail: tail)
|
|
53
|
-
end,
|
|
54
|
-
out,
|
|
55
|
-
err
|
|
56
|
-
]
|
|
57
|
-
end
|
|
58
|
-
else
|
|
59
|
-
def self.opts_from(theme)
|
|
60
|
-
out = theme.mark(:sh_out)
|
|
61
|
-
err = theme.mark(:sh_err)
|
|
62
|
-
[
|
|
63
|
-
{ bbcode: false, prefix: out, prefix_width: out.width },
|
|
64
|
-
{ bbcode: false, prefix: err, prefix_width: err.width }
|
|
65
|
-
]
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def self.run(parent, cmd, options, _space, _tail)
|
|
69
|
-
theme = Theme.current
|
|
70
|
-
opref, epref = theme.mark(:sh_out), theme.mark(:sh_err)
|
|
71
|
-
out, err = [], []
|
|
72
|
-
[
|
|
73
|
-
Terminal.sh(*cmd, **options) do |line, kind|
|
|
74
|
-
ol = line.gsub("\t", ' ').gsub(/[[:space:]]/, ' ')
|
|
75
|
-
if kind == :error
|
|
76
|
-
err << line
|
|
77
|
-
parent.puts("#{epref}#{ol}", bbcode: false)
|
|
78
|
-
else
|
|
79
|
-
out << line
|
|
80
|
-
parent.puts("#{opref}#{ol}", bbcode: false)
|
|
81
|
-
end
|
|
82
|
-
end,
|
|
83
|
-
out,
|
|
84
|
-
err
|
|
85
|
-
]
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
private_constant :ShellRenderer
|
|
91
|
-
end
|
data/lib/natty-ui/table.rb
DELETED
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'attributes'
|
|
4
|
-
require_relative 'table_renderer'
|
|
5
|
-
|
|
6
|
-
module NattyUI
|
|
7
|
-
# @todo This chapter needs more documentation.
|
|
8
|
-
#
|
|
9
|
-
# Collection of rows and columns used by {Features.table}.
|
|
10
|
-
#
|
|
11
|
-
class Table
|
|
12
|
-
class Column < NattyUI::Attributes
|
|
13
|
-
include Width
|
|
14
|
-
|
|
15
|
-
# @return [Integer] column index
|
|
16
|
-
attr_reader :index
|
|
17
|
-
|
|
18
|
-
def width
|
|
19
|
-
@width ||= find_width
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def to_s = "#{super.chop} @index:#{@index} @width:#{width.inspect}>"
|
|
23
|
-
alias inspect to_s
|
|
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
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def find_width
|
|
34
|
-
min = max = nil
|
|
35
|
-
@parent.each_cell_of(@index) do |cell|
|
|
36
|
-
next unless cell
|
|
37
|
-
m = cell.attributes.min_width
|
|
38
|
-
min = m if m && (min.nil? || m < min)
|
|
39
|
-
m = cell.attributes.max_width
|
|
40
|
-
max = m if m && (max.nil? || max < m)
|
|
41
|
-
end
|
|
42
|
-
wh_from(min, max)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def respond_to_missing?(name, _)
|
|
46
|
-
return suoer unless name.end_with?('=')
|
|
47
|
-
Cell::Attributes.public_method_defined?(name) || super
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def method_missing(name, *args, **kwargs)
|
|
51
|
-
return super unless name.end_with?('=')
|
|
52
|
-
return super unless Cell::Attributes.public_method_defined?(name)
|
|
53
|
-
@parent.each_cell_of(@index) { _1.attributes.__send__(name, *args) }
|
|
54
|
-
args[0]
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def initialize(parent, index, **attributes)
|
|
58
|
-
super(**attributes)
|
|
59
|
-
@parent = parent
|
|
60
|
-
@index = index
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
class ColumnsCollection
|
|
65
|
-
include Enumerable
|
|
66
|
-
|
|
67
|
-
# @return [Integer] count of columns
|
|
68
|
-
def count = columns.size
|
|
69
|
-
|
|
70
|
-
def empty? = columns.empty?
|
|
71
|
-
|
|
72
|
-
# @return [Column, nil] column at given index
|
|
73
|
-
def [](index) = columns[index]
|
|
74
|
-
|
|
75
|
-
def each(&block) = columns.each(&block)
|
|
76
|
-
|
|
77
|
-
def to_s = "#{super.chop} @columns:#{columns.inspect}>"
|
|
78
|
-
alias inspect to_s
|
|
79
|
-
|
|
80
|
-
private
|
|
81
|
-
|
|
82
|
-
def columns
|
|
83
|
-
cc = @parent.column_count
|
|
84
|
-
case cc <=> @columns.size
|
|
85
|
-
when -1
|
|
86
|
-
@columns = @columns.take(cc)
|
|
87
|
-
when 1
|
|
88
|
-
bi = @columns.size
|
|
89
|
-
@columns +=
|
|
90
|
-
Array.new(cc - @columns.size) { Column.new(@parent, bi + _1) }
|
|
91
|
-
else
|
|
92
|
-
@columns
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def initialize(parent)
|
|
97
|
-
@parent = parent
|
|
98
|
-
@columns = []
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def initialize_copy(*_)
|
|
102
|
-
super
|
|
103
|
-
@columns = []
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
class Row
|
|
108
|
-
include Enumerable
|
|
109
|
-
|
|
110
|
-
def empty? = @cells.empty?
|
|
111
|
-
|
|
112
|
-
# @return [Integer] count of cells
|
|
113
|
-
def count = @cells.size
|
|
114
|
-
|
|
115
|
-
def each(&block) = @cells.each(&block)
|
|
116
|
-
|
|
117
|
-
# @return [Cell, nil] cell at given index
|
|
118
|
-
def [](index) = @cells[index]
|
|
119
|
-
|
|
120
|
-
# @return [Cell] created cell
|
|
121
|
-
def []=(index, *args)
|
|
122
|
-
@cells[index] = create_cell(args)
|
|
123
|
-
@cells.map! { _1 || Cell.new }
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Add a new cell to the row with given `text` and `attributes`.
|
|
127
|
-
# @return [Cell] created cell
|
|
128
|
-
def add(*text, **attributes)
|
|
129
|
-
nc = Cell.new(*text, **attributes)
|
|
130
|
-
@cells << nc
|
|
131
|
-
block_given? ? yield(nc) : nc
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def delete(cell)
|
|
135
|
-
cell.is_a?(Cell) ? @cells.delete(cell) : @cells.delete_at(cell)
|
|
136
|
-
self
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Add a new cell to the row with given `text`.
|
|
140
|
-
# @return [Row] itself
|
|
141
|
-
def <<(text)
|
|
142
|
-
add(text)
|
|
143
|
-
self
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def assign(**attributes)
|
|
147
|
-
return self if attributes.empty?
|
|
148
|
-
@cells.each { _1.attributes.merge!(**attributes) }
|
|
149
|
-
self
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
private
|
|
153
|
-
|
|
154
|
-
def respond_to_missing?(name, _)
|
|
155
|
-
return super unless name.end_with?('=')
|
|
156
|
-
Cell::Attributes.public_method_defined?(name) || super
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def method_missing(name, *args, **_)
|
|
160
|
-
return super unless name.end_with?('=')
|
|
161
|
-
return super unless Cell::Attributes.public_method_defined?(name)
|
|
162
|
-
@cells.each { _1.attributes.__send__(name, *args) }
|
|
163
|
-
args[0]
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def initialize
|
|
167
|
-
@cells = []
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def initialize_copy(*_)
|
|
171
|
-
super
|
|
172
|
-
@cells = @cells.map(&:dup)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def create_cell(args)
|
|
176
|
-
args.flatten!(1)
|
|
177
|
-
Cell.new(*args)
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
class Cell
|
|
182
|
-
include TextWithAttributes
|
|
183
|
-
|
|
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
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
class Attributes < NattyUI::Attributes
|
|
217
|
-
prepend Border
|
|
218
|
-
prepend BorderStyle
|
|
219
|
-
prepend Position
|
|
220
|
-
|
|
221
|
-
# Whether the table has a border around.
|
|
222
|
-
#
|
|
223
|
-
# @return [true, false]
|
|
224
|
-
attr_reader :border_around
|
|
225
|
-
|
|
226
|
-
# @attribute [w] border_around
|
|
227
|
-
def border_around=(value)
|
|
228
|
-
@border_around = value ? true : false
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
# Maximum table width.
|
|
232
|
-
#
|
|
233
|
-
# @return [Integer, nil]
|
|
234
|
-
attr_reader :max_width
|
|
235
|
-
|
|
236
|
-
# @attribute [w] max_width
|
|
237
|
-
def max_width=(value)
|
|
238
|
-
if value.is_a?(Float)
|
|
239
|
-
return @max_width = nil if value < 0
|
|
240
|
-
return @max_width = value if value < 1
|
|
241
|
-
end
|
|
242
|
-
value = value.to_i
|
|
243
|
-
@max_width = value <= 0 ? nil : value
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
protected
|
|
247
|
-
|
|
248
|
-
def _assign(opt)
|
|
249
|
-
@border_around = opt[:border_around]
|
|
250
|
-
self.max_width = opt[:max_width]
|
|
251
|
-
super
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def _store(opt)
|
|
255
|
-
opt[:border_around] = true if @border_around
|
|
256
|
-
opt[:max_width] = @max_width if @max_width
|
|
257
|
-
super
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
include Enumerable
|
|
262
|
-
include WithAttributes
|
|
263
|
-
|
|
264
|
-
# @return [ColumnsCollection] table columns
|
|
265
|
-
attr_reader :columns
|
|
266
|
-
|
|
267
|
-
def empty? = @rows.empty?
|
|
268
|
-
|
|
269
|
-
# @return [Integer] count of rows
|
|
270
|
-
def count = @rows.size
|
|
271
|
-
|
|
272
|
-
# @return [Integer] count of columns
|
|
273
|
-
def column_count = @rows.empty? ? 0 : @rows.max_by(&:count).count
|
|
274
|
-
|
|
275
|
-
def each(&block) = @rows.each(&block)
|
|
276
|
-
|
|
277
|
-
def [](row_index, column_index = nil)
|
|
278
|
-
row = @rows[row_index] or return
|
|
279
|
-
column_index ? row[column_index] : row
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
# @return [Row] created row
|
|
283
|
-
def add(*text, **attributes)
|
|
284
|
-
unless text[0].is_a?(Hash)
|
|
285
|
-
@rows << (nr = Row.new)
|
|
286
|
-
text.each { nr.add(_1, **attributes) }
|
|
287
|
-
return block_given? ? yield(nr) : nr
|
|
288
|
-
end
|
|
289
|
-
new_rows = []
|
|
290
|
-
text[0].each_pair do |key, value|
|
|
291
|
-
new_rows << (nr = Row.new)
|
|
292
|
-
@rows << nr
|
|
293
|
-
nr.add(key, **attributes)
|
|
294
|
-
nr.add(value, **attributes)
|
|
295
|
-
yield(nr) if block_given?
|
|
296
|
-
end
|
|
297
|
-
new_rows
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
def delete(row)
|
|
301
|
-
row.is_a?(Row) ? @rows.delete(row) : @rows.delete_at(row)
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
def each_cell_of(column_index)
|
|
305
|
-
return to_enum(__method__, column_index) unless block_given?
|
|
306
|
-
return if (column_index = column_index.to_i) >= column_count
|
|
307
|
-
@rows.each { yield(_1[column_index]) }
|
|
308
|
-
nil
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
private
|
|
312
|
-
|
|
313
|
-
def initialize(**attributes)
|
|
314
|
-
super
|
|
315
|
-
@rows = []
|
|
316
|
-
@columns = ColumnsCollection.new(self)
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
def initialize_copy(*_)
|
|
320
|
-
super
|
|
321
|
-
@columns = ColumnsCollection.new(self)
|
|
322
|
-
@rows = @rows.map(&:dup)
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
end
|