natty-ui 0.6.0 → 0.8.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/.yardopts +12 -0
- data/README.md +25 -48
- data/examples/24bit-colors.rb +23 -0
- data/examples/3bit-colors.rb +13 -0
- data/examples/8bit-colors.rb +31 -0
- data/examples/attributes.rb +41 -134
- data/examples/demo.rb +217 -0
- data/examples/illustration.png +0 -0
- data/examples/illustration.rb +26 -0
- data/examples/list_in_columns.rb +10 -17
- data/examples/progress.rb +24 -29
- data/examples/query.rb +22 -21
- data/examples/table.rb +18 -0
- data/lib/natty-ui/ansi.rb +111 -92
- data/lib/natty-ui/ansi_wrapper.rb +93 -161
- data/lib/natty-ui/features.rb +12 -19
- data/lib/natty-ui/version.rb +2 -2
- data/lib/natty-ui/wrapper/ask.rb +26 -20
- data/lib/natty-ui/wrapper/element.rb +19 -23
- data/lib/natty-ui/wrapper/framed.rb +23 -18
- data/lib/natty-ui/wrapper/heading.rb +26 -53
- data/lib/natty-ui/wrapper/horizontal_rule.rb +37 -0
- data/lib/natty-ui/wrapper/list_in_columns.rb +66 -10
- data/lib/natty-ui/wrapper/message.rb +20 -27
- data/lib/natty-ui/{mixins.rb → wrapper/mixins.rb} +2 -2
- data/lib/natty-ui/wrapper/progress.rb +11 -9
- data/lib/natty-ui/wrapper/query.rb +37 -30
- data/lib/natty-ui/wrapper/quote.rb +25 -0
- data/lib/natty-ui/wrapper/request.rb +27 -5
- data/lib/natty-ui/wrapper/section.rb +42 -40
- data/lib/natty-ui/wrapper/table.rb +298 -0
- data/lib/natty-ui/wrapper/task.rb +9 -12
- data/lib/natty-ui/wrapper.rb +117 -44
- data/lib/natty-ui.rb +48 -50
- data/lib/natty_ui.rb +3 -0
- metadata +18 -9
- data/examples/basic.rb +0 -62
@@ -1,87 +1,60 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'element'
|
4
4
|
|
5
5
|
module NattyUI
|
6
6
|
module Features
|
7
|
-
#
|
7
|
+
# Prints a H1 title.
|
8
8
|
#
|
9
|
-
# @param
|
10
|
-
# @
|
11
|
-
|
12
|
-
# @return [Wrapper::Heading] itself, when no code block is given
|
13
|
-
def h1(title, *args, &block)
|
14
|
-
_section(self, :Heading, args, title: title, weight: 1, &block)
|
15
|
-
end
|
9
|
+
# @param [#to_s] title text
|
10
|
+
# @return [Wrapper::Section, Wrapper] it's parent object
|
11
|
+
def h1(title) = _element(:Heading, title, '═══════')
|
16
12
|
|
17
|
-
#
|
13
|
+
# Prints a H2 title.
|
18
14
|
#
|
19
|
-
# @param (see #
|
20
|
-
# @yieldparam (see #h1)
|
15
|
+
# @param (see #h1)
|
21
16
|
# @return (see #h1)
|
22
|
-
def h2(title,
|
23
|
-
_section(self, :Heading, args, title: title, weight: 2, &block)
|
24
|
-
end
|
17
|
+
def h2(title) = _element(:Heading, title, '━━━━━')
|
25
18
|
|
26
|
-
#
|
19
|
+
# Prints a H3 title.
|
27
20
|
#
|
28
|
-
# @param (see #
|
29
|
-
# @yieldparam (see #h1)
|
21
|
+
# @param (see #h1)
|
30
22
|
# @return (see #h1)
|
31
|
-
def h3(title,
|
32
|
-
_section(self, :Heading, args, title: title, weight: 3, &block)
|
33
|
-
end
|
23
|
+
def h3(title) = _element(:Heading, title, '━━━')
|
34
24
|
|
35
|
-
#
|
25
|
+
# Prints a H4 title.
|
36
26
|
#
|
37
|
-
# @param (see #
|
38
|
-
# @yieldparam (see #h1)
|
27
|
+
# @param (see #h1)
|
39
28
|
# @return (see #h1)
|
40
|
-
def h4(title,
|
41
|
-
_section(self, :Heading, args, title: title, weight: 4, &block)
|
42
|
-
end
|
29
|
+
def h4(title) = _element(:Heading, title, '───')
|
43
30
|
|
44
|
-
#
|
31
|
+
# Prints a H5 title.
|
45
32
|
#
|
46
|
-
# @param (see #
|
47
|
-
# @yieldparam (see #h1)
|
33
|
+
# @param (see #h1)
|
48
34
|
# @return (see #h1)
|
49
|
-
def h5(title,
|
50
|
-
_section(self, :Heading, args, title: title, weight: 5, &block)
|
51
|
-
end
|
35
|
+
def h5(title) = _element(:Heading, title, '──')
|
52
36
|
end
|
53
37
|
|
54
38
|
class Wrapper
|
55
39
|
#
|
56
|
-
# A {
|
40
|
+
# A {Element} drawing a title.
|
57
41
|
#
|
58
42
|
# @see Features#h1
|
59
43
|
# @see Features#h2
|
60
44
|
# @see Features#h3
|
61
45
|
# @see Features#h4
|
62
46
|
# @see Features#h5
|
63
|
-
class Heading <
|
47
|
+
class Heading < Element
|
64
48
|
protected
|
65
49
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
enclose = ENCLOSE[weight]
|
74
|
-
return "#{enclose} ", " #{enclose}" if enclose
|
75
|
-
raise(ArgumentError, "invalid heading weight - #{weight}")
|
50
|
+
def call(title, enclose)
|
51
|
+
@parent.puts(
|
52
|
+
title,
|
53
|
+
prefix: "#{Ansi[39]}#{enclose} #{Ansi[:bold, 255]}",
|
54
|
+
suffix: " #{Ansi[:normal, 39]}#{enclose}#{Ansi::RESET}",
|
55
|
+
max_width: available_width - 2 - (enclose.size * 2)
|
56
|
+
)
|
76
57
|
end
|
77
|
-
|
78
|
-
ENCLOSE = {
|
79
|
-
1 => '═══════',
|
80
|
-
2 => '━━━━━',
|
81
|
-
3 => '━━━',
|
82
|
-
4 => '───',
|
83
|
-
5 => '──'
|
84
|
-
}.compare_by_identity.freeze
|
85
58
|
end
|
86
59
|
end
|
87
60
|
end
|
@@ -0,0 +1,37 @@
|
|
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 = NattyUI.display_width(symbol = symbol.to_s)
|
24
|
+
return @parent.puts if size == 0
|
25
|
+
max_width = available_width
|
26
|
+
@parent.puts(
|
27
|
+
symbol * ((max_width / size) - 1),
|
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
|
@@ -11,10 +11,38 @@ module NattyUI
|
|
11
11
|
# The non-compact format prints all columns in same width and order the list
|
12
12
|
# items row-wise.
|
13
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
|
+
# # => 1 apple 2 banana 3 blueberry 4 pineapple 5 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
|
+
#
|
14
38
|
# @param [Array<#to_s>] args items to print
|
15
39
|
# @param [Boolean] compact whether to use compact format
|
40
|
+
# @param [nil,#to_s,Integer,Symbol] glyph optional glyph used as element
|
41
|
+
# prefix
|
16
42
|
# @return [Wrapper, Wrapper::Element] itself
|
17
|
-
def ls(*args, compact: true
|
43
|
+
def ls(*args, compact: true, glyph: nil)
|
44
|
+
_element(:ListInColumns, args, compact, glyph)
|
45
|
+
end
|
18
46
|
end
|
19
47
|
|
20
48
|
class Wrapper
|
@@ -25,22 +53,48 @@ module NattyUI
|
|
25
53
|
class ListInColumns < Element
|
26
54
|
protected
|
27
55
|
|
28
|
-
def
|
56
|
+
def call(list, compact, glyph)
|
57
|
+
return @parent if list.empty?
|
29
58
|
list.flatten!
|
30
|
-
|
31
|
-
list.map!
|
59
|
+
cvt = cvt(glyph, list.size)
|
60
|
+
list.map! do |item|
|
61
|
+
Item.new(item = cvt[item], NattyUI.display_width(item))
|
62
|
+
end
|
32
63
|
if compact
|
33
|
-
each_compacted(list, available_width) {
|
64
|
+
each_compacted(list, available_width - 1) { @parent.puts(_1) }
|
65
|
+
else
|
66
|
+
each(list, available_width - 1) { @parent.puts(_1) }
|
67
|
+
end
|
68
|
+
@parent
|
69
|
+
end
|
70
|
+
|
71
|
+
def cvt(glyph, size)
|
72
|
+
case glyph
|
73
|
+
when nil, false
|
74
|
+
->(s) { NattyUI.embellish(s) }
|
75
|
+
when :hex
|
76
|
+
pad = size.to_s(16).size
|
77
|
+
glyph = 0
|
78
|
+
lambda do |s|
|
79
|
+
"#{(glyph += 1).to_s(16).rjust(pad, '0')} #{NattyUI.embellish(s)}"
|
80
|
+
end
|
81
|
+
when Integer
|
82
|
+
pad = (glyph + size).to_s.size
|
83
|
+
glyph -= 1
|
84
|
+
->(s) { "#{(glyph += 1).to_s.rjust(pad)} #{NattyUI.embellish(s)}" }
|
85
|
+
when Symbol
|
86
|
+
lambda do |s|
|
87
|
+
"#{t = glyph; glyph = glyph.succ; t} #{NattyUI.embellish(s)}"
|
88
|
+
end
|
34
89
|
else
|
35
|
-
|
90
|
+
->(s) { "#{glyph} #{NattyUI.embellish(s)}" }
|
36
91
|
end
|
37
|
-
parent
|
38
92
|
end
|
39
93
|
|
40
94
|
def each(list, max_width)
|
41
95
|
width = list.max_by(&:width).width + 3
|
42
96
|
list.each_slice(max_width / width) do |slice|
|
43
|
-
yield(slice.map {
|
97
|
+
yield(slice.map { _1.to_s(width) }.join)
|
44
98
|
end
|
45
99
|
end
|
46
100
|
|
@@ -58,7 +112,7 @@ module NattyUI
|
|
58
112
|
widths = [list.max_by(&:width).width]
|
59
113
|
1.upto(list.size - 1) do |slice_size|
|
60
114
|
candidate = list.each_slice(list.size / slice_size).to_a
|
61
|
-
cwidths = candidate.map {
|
115
|
+
cwidths = candidate.map { _1.max_by(&:width).width + 3 }
|
62
116
|
cwidths[-1] -= 3
|
63
117
|
break if cwidths.sum > max_width
|
64
118
|
found = candidate
|
@@ -73,9 +127,11 @@ module NattyUI
|
|
73
127
|
end
|
74
128
|
|
75
129
|
Item =
|
76
|
-
|
130
|
+
Struct.new(:str, :width) do
|
77
131
|
def to_s(in_width) = "#{str}#{' ' * (in_width - width)}"
|
78
132
|
end
|
133
|
+
|
134
|
+
private_constant :Item
|
79
135
|
end
|
80
136
|
end
|
81
137
|
end
|
@@ -4,17 +4,17 @@ require_relative 'section'
|
|
4
4
|
|
5
5
|
module NattyUI
|
6
6
|
module Features
|
7
|
-
# Creates a
|
8
|
-
#
|
7
|
+
# Creates a section with a highlighted `title` and prints given additional
|
8
|
+
# arguments as lines into the section.
|
9
9
|
#
|
10
10
|
# @param [#to_s] title object to print as section title
|
11
11
|
# @param [Array<#to_s>] args more objects to print
|
12
|
-
# @param [#to_s]
|
12
|
+
# @param [#to_s] glyph glyph/prefix used for the title
|
13
13
|
# @yieldparam [Wrapper::Message] message the created section
|
14
14
|
# @return [Object] the result of the code block
|
15
15
|
# @return [Wrapper::Message] itself, when no code block is given
|
16
|
-
def message(title, *args,
|
17
|
-
_section(
|
16
|
+
def message(title, *args, glyph: :default, &block)
|
17
|
+
_section(:Message, args, title: title, glyph: glyph, &block)
|
18
18
|
end
|
19
19
|
alias msg message
|
20
20
|
|
@@ -26,7 +26,7 @@ module NattyUI
|
|
26
26
|
# @yieldparam (see #message)
|
27
27
|
# @return (see #message)
|
28
28
|
def information(title, *args, &block)
|
29
|
-
_section(
|
29
|
+
_section(:Message, args, title: title, glyph: :information, &block)
|
30
30
|
end
|
31
31
|
alias info information
|
32
32
|
|
@@ -37,7 +37,7 @@ module NattyUI
|
|
37
37
|
# @yieldparam (see #message)
|
38
38
|
# @return (see #message)
|
39
39
|
def warning(title, *args, &block)
|
40
|
-
_section(
|
40
|
+
_section(:Message, args, title: title, glyph: :warning, &block)
|
41
41
|
end
|
42
42
|
alias warn warning
|
43
43
|
|
@@ -48,7 +48,7 @@ module NattyUI
|
|
48
48
|
# @yieldparam (see #message)
|
49
49
|
# @return (see #message)
|
50
50
|
def error(title, *args, &block)
|
51
|
-
_section(
|
51
|
+
_section(:Message, args, title: title, glyph: :error, &block)
|
52
52
|
end
|
53
53
|
alias err error
|
54
54
|
|
@@ -61,7 +61,7 @@ module NattyUI
|
|
61
61
|
# @yieldparam (see #message)
|
62
62
|
# @return (see #message)
|
63
63
|
def completed(title, *args, &block)
|
64
|
-
_section(
|
64
|
+
_section(:Message, args, title: title, glyph: :completed, &block)
|
65
65
|
end
|
66
66
|
alias done completed
|
67
67
|
alias ok completed
|
@@ -76,7 +76,7 @@ module NattyUI
|
|
76
76
|
# @yieldparam (see #message)
|
77
77
|
# @return (see #message)
|
78
78
|
def failed(title, *args, &block)
|
79
|
-
_section(
|
79
|
+
_section(:Message, args, title: title, glyph: :failed, &block)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -93,24 +93,17 @@ module NattyUI
|
|
93
93
|
class Message < Section
|
94
94
|
protected
|
95
95
|
|
96
|
-
def initialize(parent, title:,
|
97
|
-
|
98
|
-
|
96
|
+
def initialize(parent, title:, glyph:)
|
97
|
+
glyph = parent.wrapper.glyph(glyph) || glyph
|
98
|
+
prefix_width = NattyUI.display_width(glyph) + 1
|
99
|
+
parent.puts(
|
100
|
+
title,
|
101
|
+
prefix: "#{glyph} ",
|
102
|
+
prefix_width: prefix_width,
|
103
|
+
suffix_width: 0
|
104
|
+
)
|
105
|
+
super(parent, prefix: ' ' * prefix_width, prefix_width: prefix_width)
|
99
106
|
end
|
100
|
-
|
101
|
-
def title_attr(str, _symbol) = { prefix: "#{str} " }
|
102
|
-
def as_symbol_str(symbol) = (SYMBOL[symbol] || symbol)
|
103
|
-
|
104
|
-
SYMBOL = {
|
105
|
-
default: '•',
|
106
|
-
information: 'i',
|
107
|
-
warning: '!',
|
108
|
-
error: 'X',
|
109
|
-
completed: '✓',
|
110
|
-
failed: 'F',
|
111
|
-
query: '▶︎',
|
112
|
-
task: '➔'
|
113
|
-
}.compare_by_identity.freeze
|
114
107
|
end
|
115
108
|
end
|
116
109
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'element'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'mixins'
|
5
5
|
|
6
6
|
module NattyUI
|
7
7
|
module Features
|
@@ -14,7 +14,7 @@ module NattyUI
|
|
14
14
|
# @param [##to_f] max_value maximum value of the progress
|
15
15
|
# @return [Wrapper::Progress] the created progress element
|
16
16
|
def progress(title, max_value: nil)
|
17
|
-
|
17
|
+
_element(:Progress, title, max_value)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -29,16 +29,18 @@ module NattyUI
|
|
29
29
|
|
30
30
|
protected
|
31
31
|
|
32
|
-
def
|
33
|
-
super(parent)
|
32
|
+
def call(title, max_value)
|
34
33
|
@final_text = [title]
|
35
34
|
@max_value = [0, max_value.to_f].max if max_value
|
36
|
-
@value = 0
|
37
|
-
@progress = 0
|
35
|
+
@value = @progress = 0
|
38
36
|
draw(title)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def draw(title)
|
41
|
+
(wrapper.stream << @parent.prefix << "➔ #{title} ").flush
|
39
42
|
end
|
40
43
|
|
41
|
-
def draw(title) = (wrapper.stream << prefix << "➔ #{title} ").flush
|
42
44
|
def end_draw = (wrapper.stream << "\n")
|
43
45
|
|
44
46
|
def redraw
|
@@ -53,11 +55,11 @@ module NattyUI
|
|
53
55
|
end_draw
|
54
56
|
return @parent.failed(*@final_text) if failed?
|
55
57
|
_section(
|
56
|
-
@parent,
|
57
58
|
:Message,
|
58
59
|
@final_text,
|
60
|
+
owner: @parent,
|
59
61
|
title: @final_text.shift,
|
60
|
-
|
62
|
+
glyph: @status = :completed
|
61
63
|
)
|
62
64
|
end
|
63
65
|
end
|
@@ -7,7 +7,7 @@ module NattyUI
|
|
7
7
|
# Request a choice from user.
|
8
8
|
#
|
9
9
|
# @example Select by Index
|
10
|
-
# choice =
|
10
|
+
# choice = ui.query(
|
11
11
|
# 'Which fruits do you prefer?',
|
12
12
|
# 'Apples',
|
13
13
|
# 'Bananas',
|
@@ -16,7 +16,7 @@ module NattyUI
|
|
16
16
|
# # => '1' or '2' or '3' or nil if user aborted
|
17
17
|
#
|
18
18
|
# @example Select by given char
|
19
|
-
# choice =
|
19
|
+
# choice = ui.query(
|
20
20
|
# 'Which fruits do you prefer?',
|
21
21
|
# a: 'Apples',
|
22
22
|
# b: 'Bananas',
|
@@ -29,13 +29,14 @@ module NattyUI
|
|
29
29
|
# @param question [#to_s] Question to display
|
30
30
|
# @param choices [#to_s] choices selectable via index (0..9)
|
31
31
|
# @param result [Symbol] defines how the result will be returned
|
32
|
+
# @param display [Symbol] display choices as `:list` or `:compact`
|
32
33
|
# @param kw_choices [{Char => #to_s}] choices selectable with given char
|
33
34
|
# @return [Char] when `result` is configured as `:char`
|
34
35
|
# @return [#to_s] when `result` is configured as `:choice`
|
35
36
|
# @return [[Char, #to_s]] when `result` is configured as `:both`
|
36
|
-
# @return [nil] when input was aborted with
|
37
|
-
def query(question, *choices, result: :char, **kw_choices)
|
38
|
-
_element(:Query, question, choices, kw_choices, result)
|
37
|
+
# @return [nil] when input was aborted with `^C` or `^D`
|
38
|
+
def query(question, *choices, result: :char, display: :list, **kw_choices)
|
39
|
+
_element(:Query, question, choices, kw_choices, result, display)
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
@@ -47,41 +48,47 @@ module NattyUI
|
|
47
48
|
class Query < Element
|
48
49
|
protected
|
49
50
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
:
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
)
|
61
|
-
read(choices, result_typye)
|
51
|
+
def call(question, choices, kw_choices, result, display)
|
52
|
+
return if choices.empty? && kw_choices.empty?
|
53
|
+
choices = as_choices(choices, kw_choices)
|
54
|
+
text = choices.map { |k, v| "⦗#{CHOICE_MARK}#{k}#{Ansi::RESET}⦘ #{v}" }
|
55
|
+
@parent.wrapper.temporary do
|
56
|
+
if display == :compact
|
57
|
+
@parent.msg(question, glyph: :query).ls(text)
|
58
|
+
else
|
59
|
+
@parent.msg(question, *text, glyph: :query)
|
60
|
+
end
|
61
|
+
read(choices, result)
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
def
|
65
|
+
def as_choices(choices, kw_choices)
|
66
|
+
ret = {}
|
67
|
+
choices.each_with_index do |title, i|
|
68
|
+
(i += 1) == 10 ? break : ret[i.to_s] = title.to_s.tr("\r\n\t", ' ')
|
69
|
+
end
|
70
|
+
ret.merge!(
|
71
|
+
kw_choices
|
72
|
+
.transform_keys! { [' ', _1.to_s[0]].max }
|
73
|
+
.transform_values! { _1.to_s.tr("\r\n\t", ' ') }
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def read(choices, result)
|
66
78
|
while true
|
67
79
|
char = NattyUI.in_stream.getch
|
68
|
-
return if "\3\4
|
80
|
+
return if "\3\4".include?(char)
|
69
81
|
next unless choices.key?(char)
|
70
|
-
return char if
|
71
|
-
return choices[char] if
|
82
|
+
return char if result == :char
|
83
|
+
return choices[char] if result == :title
|
72
84
|
return char, choices[char]
|
73
85
|
end
|
86
|
+
rescue Interrupt, SystemCallError
|
87
|
+
nil
|
74
88
|
end
|
75
89
|
|
76
|
-
|
77
|
-
|
78
|
-
.new(choices.size) { |i| i + 1 }
|
79
|
-
.zip(choices)
|
80
|
-
.to_h
|
81
|
-
.merge!(kw_choices)
|
82
|
-
.transform_keys! { |k| [k.to_s[0], ' '].max }
|
83
|
-
.transform_values! { |v| v.to_s.tr("\r\n\t", ' ') }
|
84
|
-
end
|
90
|
+
CHOICE_MARK = Ansi[:bold, 34]
|
91
|
+
private_constant :CHOICE_MARK
|
85
92
|
end
|
86
93
|
end
|
87
94
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'element'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
module Features
|
7
|
+
# Creates a quotation section and prints given arguments as lines
|
8
|
+
# into the section.
|
9
|
+
#
|
10
|
+
# @param (see #section)
|
11
|
+
# @yieldparam (see #section)
|
12
|
+
# @return [Object] the result of the code block
|
13
|
+
# @return [Wrapper::Quote] itself, when no code block is given
|
14
|
+
def quote(*args, &block) = _section(:Quote, args, prefix: '▍ ', &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
class Wrapper
|
18
|
+
#
|
19
|
+
# A quotation {Section}.
|
20
|
+
#
|
21
|
+
# @see Features#quote
|
22
|
+
class Quote < Section
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -7,9 +7,12 @@ module NattyUI
|
|
7
7
|
# Request user input.
|
8
8
|
#
|
9
9
|
# @param question [#to_s] Question to display
|
10
|
+
# @param password [Boolean] whether to hide the input
|
10
11
|
# @return [String] the user input
|
11
12
|
# @return [nil] when input was aborted with `^C` or `^D`
|
12
|
-
def request(question
|
13
|
+
def request(question, password: false)
|
14
|
+
_element(:Request, question, password)
|
15
|
+
end
|
13
16
|
end
|
14
17
|
|
15
18
|
class Wrapper
|
@@ -20,13 +23,32 @@ module NattyUI
|
|
20
23
|
class Request < Element
|
21
24
|
protected
|
22
25
|
|
23
|
-
def
|
24
|
-
|
26
|
+
def call(question, password)
|
27
|
+
draw(question)
|
28
|
+
return NattyUI.in_stream.getpass if password
|
29
|
+
NattyUI.in_stream.gets(chomp: true)
|
30
|
+
rescue Interrupt, SystemCallError
|
31
|
+
nil
|
25
32
|
ensure
|
26
|
-
|
33
|
+
(wrapper = @parent.wrapper).ansi? and
|
34
|
+
(wrapper.stream << ANSI_FINISH).flush
|
35
|
+
end
|
36
|
+
|
37
|
+
def draw(question)
|
38
|
+
wrapper = @parent.wrapper
|
39
|
+
glyph = wrapper.glyph(:query)
|
40
|
+
@parent.print(
|
41
|
+
question,
|
42
|
+
prefix: "#{glyph} #{Ansi[255]}",
|
43
|
+
prefix_width: NattyUI.display_width(glyph) + 1,
|
44
|
+
suffix_width: 0
|
45
|
+
)
|
46
|
+
(wrapper.stream << ANSI_PREFIX).flush if wrapper.ansi?
|
27
47
|
end
|
28
48
|
|
29
|
-
|
49
|
+
ANSI_PREFIX = Ansi::RESET + Ansi[:italic]
|
50
|
+
ANSI_FINISH = Ansi::RESET + Ansi::CURSOR_UP + Ansi::LINE_ERASE
|
51
|
+
private_constant :ANSI_PREFIX, :ANSI_FINISH
|
30
52
|
end
|
31
53
|
end
|
32
54
|
end
|