natty-ui 0.34.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/.yardopts +0 -1
- 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 -5
- 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 +27 -17
- 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 +64 -65
- data/lib/natty-ui/features.rb +773 -872
- 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 -149
- 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 +142 -41
- data/lib/natty-ui/task.rb +39 -27
- 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 +87 -30
- 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 -51
- 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
data/lib/natty-ui/section.rb
CHANGED
|
@@ -1,66 +1,167 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'element'
|
|
4
|
+
require_relative 'utils/border'
|
|
4
5
|
|
|
5
6
|
module NattyUI
|
|
6
|
-
#
|
|
7
|
+
# An {Element} that draws a bordered container with an optional title.
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# - {Features.error}
|
|
13
|
-
# - {Features.failed}
|
|
9
|
+
# Instances are created by {#section} and its typed shorthand variants:
|
|
10
|
+
# {#message}, {#information}, {#warning}, {#error}, and {#fatal}.
|
|
11
|
+
# The visual style (border colour, prefix mark) is determined by the `type:`
|
|
12
|
+
# argument.
|
|
14
13
|
#
|
|
14
|
+
# All {Features} methods are available on this element.
|
|
15
|
+
#
|
|
16
|
+
# @example Via block (auto-close)
|
|
17
|
+
# ui.section 'Summary', type: :information do
|
|
18
|
+
# ui.puts '3 files processed.'
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @example Manual close
|
|
22
|
+
# sec = ui.warning 'Disk space low'
|
|
23
|
+
# ui.puts 'Only 500 MB remaining.'
|
|
24
|
+
# sec.end
|
|
15
25
|
class Section < Element
|
|
16
|
-
|
|
26
|
+
# @private
|
|
27
|
+
def puts(...) = @done == 0 ? super : self
|
|
28
|
+
|
|
29
|
+
# @private
|
|
30
|
+
def hr(kind = nil)
|
|
31
|
+
return self if @done != 0
|
|
32
|
+
width = @parent.columns
|
|
33
|
+
return _hr_border(width, @type.border) if kind.nil?
|
|
34
|
+
return _hr_border(width, Border[kind]) if kind.is_a?(Symbol)
|
|
35
|
+
kind = StrConst[kind]
|
|
36
|
+
return _hr_border(width, @type.border) if kind.width == 0
|
|
37
|
+
width -= 2
|
|
38
|
+
puts(
|
|
39
|
+
"#{@type.style}#{
|
|
40
|
+
if kind.width > width
|
|
41
|
+
kind.to_str[0, width]
|
|
42
|
+
elsif kind.width == width
|
|
43
|
+
kind
|
|
44
|
+
else
|
|
45
|
+
kind.to_str * (width / kind.width)
|
|
46
|
+
end
|
|
47
|
+
}"
|
|
48
|
+
)
|
|
49
|
+
end
|
|
17
50
|
|
|
18
51
|
# @private
|
|
19
|
-
def
|
|
52
|
+
def heading(level, title)
|
|
53
|
+
return self if @done != 0
|
|
54
|
+
width = @parent.columns
|
|
55
|
+
hr =
|
|
56
|
+
@type.border.horoe(
|
|
57
|
+
Border[level == 1 ? :double : :single],
|
|
58
|
+
width,
|
|
59
|
+
@type.style
|
|
60
|
+
)
|
|
61
|
+
@parent.puts(hr) if level < 3 && @start_line != NattyUI.lines_written
|
|
62
|
+
puts(title, padding: [0, 1])
|
|
63
|
+
@parent.puts(hr)
|
|
64
|
+
self
|
|
65
|
+
end
|
|
20
66
|
|
|
21
67
|
private
|
|
22
68
|
|
|
23
|
-
def
|
|
24
|
-
puts(
|
|
25
|
-
@parent.puts(@border.bottom)
|
|
69
|
+
def done
|
|
70
|
+
@parent.puts(@type.bottom) if (@done += 1) == 1
|
|
26
71
|
end
|
|
27
72
|
|
|
28
|
-
def
|
|
29
|
-
@parent.puts(@border.
|
|
73
|
+
def _hr_border(width, border)
|
|
74
|
+
@parent.puts(@type.border.horoe(border, width, @type.style))
|
|
75
|
+
self
|
|
30
76
|
end
|
|
31
77
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
suffix: suffix,
|
|
41
|
-
suffix_width: suffix.width
|
|
78
|
+
def initialize(parent, title, kind, border)
|
|
79
|
+
super(parent)
|
|
80
|
+
@type = Type[border, kind]
|
|
81
|
+
@prefix = @type.left
|
|
82
|
+
title = normalize(title) or return parent.puts(@type.top)
|
|
83
|
+
parent.puts(
|
|
84
|
+
"#{@type.title_left}#{title}#{@type.title_right}",
|
|
85
|
+
bbcode: false
|
|
42
86
|
)
|
|
43
87
|
end
|
|
44
88
|
|
|
45
|
-
def
|
|
46
|
-
|
|
47
|
-
title
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
89
|
+
def normalize(title)
|
|
90
|
+
return if title.nil? || title.empty?
|
|
91
|
+
title =
|
|
92
|
+
Text::Formatter[
|
|
93
|
+
title,
|
|
94
|
+
eol: false,
|
|
95
|
+
width:
|
|
96
|
+
@parent.columns - @type.title_left.width - @type.title_right.width
|
|
97
|
+
].first
|
|
98
|
+
title.nil? || title.empty? ? nil : title
|
|
54
99
|
end
|
|
55
100
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
101
|
+
# @private
|
|
102
|
+
class Type
|
|
103
|
+
def self.[](border, kind) = @defined.dig(border, kind)
|
|
104
|
+
|
|
105
|
+
attr_reader :border,
|
|
106
|
+
:style,
|
|
107
|
+
:top,
|
|
108
|
+
:title_left,
|
|
109
|
+
:title_right,
|
|
110
|
+
:left,
|
|
111
|
+
:bottom
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def initialize(border, style, mark)
|
|
116
|
+
@border = border
|
|
117
|
+
@style = Ansi[*style]
|
|
118
|
+
@top =
|
|
119
|
+
StrConst[
|
|
120
|
+
"#{@style}#{border.top_left}#{border.top}#{border.extra}#{
|
|
121
|
+
Ansi::RESET
|
|
122
|
+
}"
|
|
123
|
+
]
|
|
124
|
+
@title_left =
|
|
125
|
+
StrConst[
|
|
126
|
+
"#{@style}#{
|
|
127
|
+
border.top_left
|
|
128
|
+
}#{border.top}#{border.end}#{Ansi::RESET} #{mark}"
|
|
129
|
+
]
|
|
130
|
+
@title_right =
|
|
131
|
+
StrConst[
|
|
132
|
+
"#{Ansi::RESET} #{@style}#{
|
|
133
|
+
border.begin
|
|
134
|
+
}#{border.top}#{border.extra}#{Ansi::RESET}"
|
|
135
|
+
]
|
|
136
|
+
@left = StrConst["#{@style}#{border.left}#{Ansi::RESET} "]
|
|
137
|
+
@bottom =
|
|
138
|
+
StrConst[
|
|
139
|
+
"#{@style}#{border.bottom_left}#{
|
|
140
|
+
border.bottom
|
|
141
|
+
}#{border.extra}#{Ansi::RESET}"
|
|
142
|
+
]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
@kind = {
|
|
146
|
+
default: [:bright_blue, ''],
|
|
147
|
+
message: [:bright_blue, '[bright_blue]•[/fg] '],
|
|
148
|
+
information: [:bright_blue, '[bright_blue]𝒊[/fg] '],
|
|
149
|
+
warning: [:yellow, '[bright_yellow]![/fg] '],
|
|
150
|
+
error: [:red, '[bright_red]𝙓[/fg] '],
|
|
151
|
+
fatal: [:red, '[bright_red i]𝒊[/fg /i] ']
|
|
152
|
+
}.compare_by_identity
|
|
153
|
+
@kind.default = @kind[:default]
|
|
154
|
+
|
|
155
|
+
@defined =
|
|
156
|
+
Hash
|
|
157
|
+
.new do |h1, border|
|
|
158
|
+
h1[border] = Hash
|
|
159
|
+
.new { |h2, kind| h2[kind] = new(Border[border], *@kind[kind]) }
|
|
160
|
+
.compare_by_identity
|
|
161
|
+
end
|
|
162
|
+
.compare_by_identity
|
|
64
163
|
end
|
|
164
|
+
|
|
165
|
+
private_constant :Type
|
|
65
166
|
end
|
|
66
167
|
end
|
data/lib/natty-ui/task.rb
CHANGED
|
@@ -1,48 +1,60 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '
|
|
3
|
+
require_relative 'temporary'
|
|
4
4
|
|
|
5
5
|
module NattyUI
|
|
6
|
-
#
|
|
6
|
+
# A {Temporary} element that represents a single labelled step.
|
|
7
7
|
#
|
|
8
|
+
# Instances are created by {#task}. Any content printed inside
|
|
9
|
+
# a `Task` block disappears when the element is closed. Content printed
|
|
10
|
+
# with {#pin} is exempt — it is collected and reprinted after the
|
|
11
|
+
# erasure so it remains visible.
|
|
12
|
+
#
|
|
13
|
+
# All {Features} methods are available on this element, making it easy to
|
|
14
|
+
# nest sub-tasks or progress indicators inside a task block.
|
|
15
|
+
#
|
|
16
|
+
# @example Simple task with a block
|
|
17
|
+
# ui.task 'Installing dependencies' do
|
|
18
|
+
# run_install
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @example Nested tasks
|
|
22
|
+
# ui.task 'Deploy' do
|
|
23
|
+
# ui.task('Build') { compile }
|
|
24
|
+
# ui.task('Upload') { upload }
|
|
25
|
+
# ui.pin 'Deployed to production', mark: :checkmark
|
|
26
|
+
# end
|
|
8
27
|
class Task < Temporary
|
|
9
|
-
|
|
28
|
+
# @private
|
|
29
|
+
def fatal(...)
|
|
30
|
+
return self if (@done += 1) != 1
|
|
31
|
+
@start_line = nil
|
|
32
|
+
self.end
|
|
33
|
+
@parent.fatal(...)
|
|
34
|
+
end
|
|
10
35
|
|
|
11
36
|
private
|
|
12
37
|
|
|
13
|
-
def
|
|
38
|
+
def done
|
|
39
|
+
return self if (@done += 1) != 1 || @start_line.nil?
|
|
14
40
|
NattyUI.back_to_line(@start_line)
|
|
15
|
-
@start_line = nil
|
|
16
|
-
cm = Theme.current.mark(:checkmark)
|
|
17
41
|
@parent.puts(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
42
|
+
@title,
|
|
43
|
+
prefix: @prefix,
|
|
44
|
+
cprefix: Utils.mark[:checkmark],
|
|
21
45
|
pin: @pin
|
|
22
46
|
)
|
|
23
|
-
@pins&.each { |objects,
|
|
47
|
+
@pins&.each { |objects, opts| @parent.puts(*objects, **opts) }
|
|
24
48
|
end
|
|
25
49
|
|
|
26
|
-
def
|
|
27
|
-
@start_line = nil
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def initialize(parent, title, msg, pin)
|
|
50
|
+
def initialize(parent, title, pin)
|
|
31
51
|
super(parent)
|
|
32
|
-
@title = title
|
|
33
|
-
@pin = pin
|
|
34
|
-
style = Theme.current.task_style
|
|
35
|
-
cm = Theme.current.mark(:current)
|
|
36
|
-
@prefix = ' ' * cm.width
|
|
37
|
-
@prefix_width = cm.width
|
|
38
52
|
parent.puts(
|
|
39
|
-
title,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
prefix: "#{@prefix}#{style}",
|
|
43
|
-
prefix_width: cm.width
|
|
53
|
+
@title = title,
|
|
54
|
+
cprefix: Utils.mark[:current],
|
|
55
|
+
prefix: @prefix = Utils.mark[:none]
|
|
44
56
|
)
|
|
45
|
-
|
|
57
|
+
@pin = pin
|
|
46
58
|
end
|
|
47
59
|
end
|
|
48
60
|
end
|
data/lib/natty-ui/temporary.rb
CHANGED
|
@@ -3,32 +3,45 @@
|
|
|
3
3
|
require_relative 'element'
|
|
4
4
|
|
|
5
5
|
module NattyUI
|
|
6
|
-
#
|
|
6
|
+
# An {Element} whose output is erased from the terminal when it is closed.
|
|
7
7
|
#
|
|
8
|
+
# Instances are created by {#temporary}. Any content printed inside
|
|
9
|
+
# a `Temporary` block disappears when the element is closed.
|
|
10
|
+
# Content printed with {#pin} is exempt — it is collected and reprinted after
|
|
11
|
+
# the erasure so it remains visible.
|
|
12
|
+
#
|
|
13
|
+
# All {Features} methods are available on this element.
|
|
14
|
+
#
|
|
15
|
+
# @example Via block (auto-close)
|
|
16
|
+
# ui.temporary do
|
|
17
|
+
# ui.puts 'Thinking…'
|
|
18
|
+
# sleep 2
|
|
19
|
+
# end # "Thinking…" is erased here
|
|
20
|
+
#
|
|
21
|
+
# @example Manual close
|
|
22
|
+
# ui.temporary
|
|
23
|
+
# ui.puts 'Loading…'
|
|
24
|
+
# do_work
|
|
25
|
+
# ui.end # erases "Loading…"
|
|
8
26
|
class Temporary < Element
|
|
9
27
|
# @private
|
|
10
28
|
def puts(*objects, **opts)
|
|
11
|
-
return self if @
|
|
12
|
-
if opts.delete(:pin)
|
|
13
|
-
(@pins ||= []) << [objects, opts.except(:prefix_width, :suffix_width)]
|
|
14
|
-
end
|
|
29
|
+
return self if @done != 0
|
|
30
|
+
(@pins ||= []) << [objects, opts.dup] if opts.delete(:pin)
|
|
15
31
|
super
|
|
16
32
|
end
|
|
17
33
|
|
|
18
|
-
|
|
34
|
+
private
|
|
35
|
+
|
|
19
36
|
def done
|
|
20
|
-
return self if @
|
|
21
|
-
NattyUI.back_to_line(@start_line)
|
|
22
|
-
@pins&.each { |objects, opts| puts(*objects, **opts) }
|
|
23
|
-
@state = :ok
|
|
24
|
-
self
|
|
37
|
+
return self if (@done += 1) != 1
|
|
38
|
+
NattyUI.back_to_line(@start_line)
|
|
39
|
+
@pins&.each { |objects, opts| @parent.puts(*objects, **opts) }
|
|
25
40
|
end
|
|
26
41
|
|
|
27
|
-
private
|
|
28
|
-
|
|
29
42
|
def initialize(parent)
|
|
30
|
-
@start_line = NattyUI.lines_written
|
|
31
43
|
super
|
|
44
|
+
@start_line = NattyUI.lines_written
|
|
32
45
|
end
|
|
33
46
|
end
|
|
34
47
|
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NattyUI
|
|
4
|
+
# @private
|
|
5
|
+
class Border
|
|
6
|
+
def self.defined = @defined.keys
|
|
7
|
+
def self.[](name) = @defined[name]
|
|
8
|
+
def self.connections(kind) = @connections[kind]
|
|
9
|
+
|
|
10
|
+
attr_reader :to_str, :extra
|
|
11
|
+
|
|
12
|
+
def left = @to_str[0]
|
|
13
|
+
def right = @to_str[0]
|
|
14
|
+
def top = @to_str[1]
|
|
15
|
+
def bottom = @to_str[1]
|
|
16
|
+
def top_left = @to_str[2]
|
|
17
|
+
def top_right = @to_str[3]
|
|
18
|
+
def bottom_left = @to_str[4]
|
|
19
|
+
def bottom_right = @to_str[5]
|
|
20
|
+
|
|
21
|
+
def vert_for(other) = @vert[select(other)]
|
|
22
|
+
def hor_for(other) = @hor[select(other)]
|
|
23
|
+
def inter_for(other) = @inter[select(other)]
|
|
24
|
+
|
|
25
|
+
def build(width, title, style)
|
|
26
|
+
return StrConst.empty, StrConst.empty if width < 3
|
|
27
|
+
elyts = style.empty? ? '' : Ansi::RESET
|
|
28
|
+
line = @to_str[1] * (width - 2)
|
|
29
|
+
if title && width > 8
|
|
30
|
+
title, size =
|
|
31
|
+
Text::Formatter[
|
|
32
|
+
title,
|
|
33
|
+
with_size: true,
|
|
34
|
+
eol: false,
|
|
35
|
+
width: width - 7
|
|
36
|
+
].first
|
|
37
|
+
if size&.nonzero?
|
|
38
|
+
top =
|
|
39
|
+
"#{style}#{@to_str[2]}#{@to_str[1] * 2}#{elyts} #{
|
|
40
|
+
title
|
|
41
|
+
} #{style}#{@to_str[1] * (width - 6 - size)}#{@to_str[3]}#{elyts}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
[
|
|
45
|
+
lur = StrConst.new("#{style}#{@to_str[0]}#{elyts}", 1),
|
|
46
|
+
lur,
|
|
47
|
+
top || "#{style}#{@to_str[2]}#{line}#{@to_str[3]}#{elyts}",
|
|
48
|
+
"#{style}#{@to_str[4]}#{line}#{@to_str[5]}#{elyts}"
|
|
49
|
+
]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def hor(other, width, style)
|
|
53
|
+
return if width < 3
|
|
54
|
+
hor = @hor[select(other)]
|
|
55
|
+
ret = "#{hor[1]}#{hor[0] * (width - 2)}#{hor[2]}"
|
|
56
|
+
style.empty? ? ret : "#{style}#{ret}#{Ansi::RESET}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def horoe(other, width, style)
|
|
60
|
+
return if width < 2
|
|
61
|
+
hor = @hor[select(other)]
|
|
62
|
+
ret = "#{hor[1]}#{hor[0] * (width - 1)}"
|
|
63
|
+
style.empty? ? ret : "#{style}#{ret}#{Ansi::RESET}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def vert(other, style)
|
|
67
|
+
vert = @vert[select(other)].chars
|
|
68
|
+
style.empty? ? vert : vert.map! { "#{style}#{it}#{Ansi::RESET}" }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def begin = (@begin ||= @hor[@to_str[1]][1])
|
|
72
|
+
def end = (@end ||= @hor[@to_str[1]][2])
|
|
73
|
+
|
|
74
|
+
def intersection(other, style)
|
|
75
|
+
ret = @inter[select(other)]
|
|
76
|
+
style.empty? ? ret : "#{style}#{ret}#{Ansi::RESET}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def initialize(to_str)
|
|
82
|
+
@to_str = to_str
|
|
83
|
+
@vert, @hor, @inter, @extra = Border.connections(to_str[0])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def select(other)
|
|
87
|
+
case other
|
|
88
|
+
when Border
|
|
89
|
+
other.top
|
|
90
|
+
when Symbol
|
|
91
|
+
Border[other].top
|
|
92
|
+
when String
|
|
93
|
+
other[0]
|
|
94
|
+
else
|
|
95
|
+
@to_str[1]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def_hwd = ->(hs) do
|
|
100
|
+
hs.default = hs[hs.keys.first]
|
|
101
|
+
hs.freeze
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
@connections = {
|
|
105
|
+
'│' => [
|
|
106
|
+
def_hwd['─' => '│┬┴', '═' => '│╤╧', '━' => '│┯┷'],
|
|
107
|
+
def_hwd['─' => '─├┤', '═' => '═╞╡', '━' => '━┝┥'],
|
|
108
|
+
def_hwd['─' => '┼', '═' => '╪', '━' => '┿'],
|
|
109
|
+
'╴╴╴'
|
|
110
|
+
],
|
|
111
|
+
'║' => [
|
|
112
|
+
def_hwd['═' => '║╦╩', '─' => '║╥╨'],
|
|
113
|
+
def_hwd['═' => '═╠╣', '─' => '─╟╢'],
|
|
114
|
+
def_hwd['═' => '╬', '─' => '╫'],
|
|
115
|
+
' ═ '
|
|
116
|
+
],
|
|
117
|
+
'┃' => [
|
|
118
|
+
def_hwd['━' => '┃┳┻', '─' => '┃┰┸'],
|
|
119
|
+
def_hwd['━' => '━┣┫', '─' => '─┠┨'],
|
|
120
|
+
def_hwd['━' => '╋', '─' => '╂'],
|
|
121
|
+
'╸╸╸'
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@defined = {
|
|
126
|
+
rounded: new('│─╭╮╰╯'),
|
|
127
|
+
single: new('│─┌┐└┘'),
|
|
128
|
+
double: new('║═╔╗╚╝'),
|
|
129
|
+
heavy: new('┃━┏┓┗┛'),
|
|
130
|
+
single_double: new('│═╒╕╘╛'),
|
|
131
|
+
double_single: new('║─╓╖╙╜'),
|
|
132
|
+
single_heavy: new('│━┍┑┕┙'),
|
|
133
|
+
heavy_single: new('┃─┎┒┖┚')
|
|
134
|
+
}.compare_by_identity
|
|
135
|
+
@defined.default = @defined[:rounded]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private_constant :Border
|
|
139
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NattyUI
|
|
4
|
+
# @private
|
|
5
|
+
class StrConst
|
|
6
|
+
class << self
|
|
7
|
+
attr_reader :empty
|
|
8
|
+
|
|
9
|
+
def from_str(str)
|
|
10
|
+
str, width = Text::Formatter.new(str, eol: false).lines_with_size[0]
|
|
11
|
+
width == 0 ? @empty : new(str, width)
|
|
12
|
+
end
|
|
13
|
+
alias [] from_str
|
|
14
|
+
|
|
15
|
+
def spacer(size = 1)
|
|
16
|
+
size < 1 ? @empty : new((' ' * size).freeze, size)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def join(*other)
|
|
20
|
+
other.map! { ::StrConst === it ? it : from_str(it) }
|
|
21
|
+
new(other.join.freeze, other.sum(&:width))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_reader :to_s, :width
|
|
26
|
+
def empty? = (@width == 0)
|
|
27
|
+
def inspect = "<#{@to_s.inspect}, width:#{@width}>"
|
|
28
|
+
alias to_str to_s
|
|
29
|
+
|
|
30
|
+
def ljust(width)
|
|
31
|
+
return '' if (space = width - @width) < 0
|
|
32
|
+
space == 0 ? @to_s : "#{@to_s}#{' ' * space}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def rjust(width)
|
|
36
|
+
return '' if (space = width - @width) < 0
|
|
37
|
+
space == 0 ? @to_s : "#{' ' * space}#{@to_s}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def +(other)
|
|
41
|
+
other = StrConst[other] unless StrConst === other
|
|
42
|
+
StrConst.new((@to_s + other.to_s).freeze, @width + other.width)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def initialize(str, width)
|
|
46
|
+
@to_s =
|
|
47
|
+
if str.frozen?
|
|
48
|
+
str.encoding == ENCODING ? str : str.encode(ENCODING).freeze
|
|
49
|
+
else
|
|
50
|
+
str = str.dup
|
|
51
|
+
str.encode!(ENCODING) if str.encoding != ENCODING
|
|
52
|
+
str.freeze
|
|
53
|
+
end
|
|
54
|
+
@width = width
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
ENCODING = Encoding::UTF_8
|
|
58
|
+
@empty = new('', 0)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private_constant :StrConst
|
|
62
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NattyUI
|
|
4
|
+
# @private
|
|
5
|
+
module Utils
|
|
6
|
+
class << self
|
|
7
|
+
attr_reader :mark
|
|
8
|
+
|
|
9
|
+
def affix(style)
|
|
10
|
+
return '', '' unless Terminal.ansi?
|
|
11
|
+
return '', '' if (style &&= Ansi[*style]).nil? || style.empty?
|
|
12
|
+
[style, Ansi::RESET]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def as_width(value, max)
|
|
16
|
+
return max if value == :auto
|
|
17
|
+
(Float === value ? (max * value).round : value.to_int).clamp(0, max)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def as_mark(mark)
|
|
21
|
+
case mark
|
|
22
|
+
when nil
|
|
23
|
+
@mark.default
|
|
24
|
+
when Symbol
|
|
25
|
+
@mark[mark]
|
|
26
|
+
else
|
|
27
|
+
"#{mark} "
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@mark = {
|
|
33
|
+
default: StrConst['[bright_blue]•[/fg] '],
|
|
34
|
+
current: StrConst['[b bright_green]→ '],
|
|
35
|
+
active: StrConst['[b bright_green]→[/b /fg] '],
|
|
36
|
+
checkmark: StrConst['[b bright_green]✓[/b /fg] '],
|
|
37
|
+
item: StrConst['[d]•[/d] '],
|
|
38
|
+
current_item: StrConst['[b bright_green]●[/b /fg] '],
|
|
39
|
+
output: StrConst['[d]>[/d] '],
|
|
40
|
+
error: StrConst['[red]𝙓[/fg] ']
|
|
41
|
+
}.compare_by_identity
|
|
42
|
+
@mark.default = @mark[:default]
|
|
43
|
+
@mark[:none] = StrConst.spacer(@mark.default.width)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private_constant :Utils
|
|
47
|
+
end
|
data/lib/natty-ui/version.rb
CHANGED