natty-ui 0.5.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 +7 -0
- data/LICENSE +28 -0
- data/README.md +122 -0
- data/examples/basic.rb +61 -0
- data/examples/colors.rb +5 -0
- data/examples/illustration.svg +1 -0
- data/examples/progress.rb +84 -0
- data/examples/query.rb +32 -0
- data/lib/natty-ui/ansi.rb +430 -0
- data/lib/natty-ui/ansi_wrapper.rb +207 -0
- data/lib/natty-ui/version.rb +6 -0
- data/lib/natty-ui/wrapper/ask.rb +76 -0
- data/lib/natty-ui/wrapper/element.rb +77 -0
- data/lib/natty-ui/wrapper/features.rb +24 -0
- data/lib/natty-ui/wrapper/framed.rb +54 -0
- data/lib/natty-ui/wrapper/heading.rb +87 -0
- data/lib/natty-ui/wrapper/message.rb +116 -0
- data/lib/natty-ui/wrapper/mixins.rb +67 -0
- data/lib/natty-ui/wrapper/progress.rb +60 -0
- data/lib/natty-ui/wrapper/query.rb +85 -0
- data/lib/natty-ui/wrapper/section.rb +102 -0
- data/lib/natty-ui/wrapper/task.rb +58 -0
- data/lib/natty-ui/wrapper.rb +168 -0
- data/lib/natty-ui.rb +159 -0
- metadata +91 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'features'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
class Wrapper
|
7
|
+
#
|
8
|
+
# Basic visual element implementing all {Features}.
|
9
|
+
#
|
10
|
+
class Element
|
11
|
+
include Features
|
12
|
+
|
13
|
+
# @return [Section] when embedded in a section
|
14
|
+
# @return [Wrapper] when not embedded in a section
|
15
|
+
attr_reader :parent
|
16
|
+
|
17
|
+
# @return [Symbol] close status when closed
|
18
|
+
# @return [nil] when not closed
|
19
|
+
attr_reader :status
|
20
|
+
|
21
|
+
# @attribute [r] closed?
|
22
|
+
# @return [Boolean] whether its closed or not
|
23
|
+
def closed? = (@status != nil)
|
24
|
+
|
25
|
+
# Close the element.
|
26
|
+
#
|
27
|
+
# @return [Element] itself when used without a code block
|
28
|
+
# @return [nil] when used with a code block
|
29
|
+
def close = _close(:closed)
|
30
|
+
|
31
|
+
alias _to_s to_s
|
32
|
+
private :_to_s
|
33
|
+
|
34
|
+
# @!visibility private
|
35
|
+
def inspect
|
36
|
+
"#{_to_s[..-2]} status=#{@status}}}>"
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def prefix = "#{@parent.__send__(:prefix)}#{@prefix}"
|
42
|
+
def suffix = "#{@parent.__send__(:suffix)}#{@suffix}"
|
43
|
+
def finish = nil
|
44
|
+
|
45
|
+
def wrapper
|
46
|
+
return @wrapper if @wrapper
|
47
|
+
@wrapper = self
|
48
|
+
@wrapper = @wrapper.parent until @wrapper.is_a?(Wrapper)
|
49
|
+
@wrapper
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(parent)
|
53
|
+
@parent = parent
|
54
|
+
end
|
55
|
+
|
56
|
+
def _close(state)
|
57
|
+
return self if @status
|
58
|
+
@status = state
|
59
|
+
finish
|
60
|
+
@raise ? raise(BREAK) : self
|
61
|
+
end
|
62
|
+
|
63
|
+
def _call
|
64
|
+
@raise = true
|
65
|
+
yield(self)
|
66
|
+
close unless closed?
|
67
|
+
rescue BREAK
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
BREAK = Class.new(StandardError)
|
72
|
+
private_constant :BREAK
|
73
|
+
|
74
|
+
private_class_method :new
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NattyUI
|
4
|
+
#
|
5
|
+
# Mix-in for output functionality.
|
6
|
+
#
|
7
|
+
module Features
|
8
|
+
protected
|
9
|
+
|
10
|
+
def _element(type, *args)
|
11
|
+
wrapper.class.const_get(type).__send__(:new, self).__send__(:_call, *args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def _section(type, args, **opts, &block)
|
15
|
+
__section(self, type, args, **opts, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def __section(owner, type, args, **opts, &block)
|
19
|
+
sec = wrapper.class.const_get(type).__send__(:new, owner, **opts)
|
20
|
+
sec.puts(*args) if args && !args.empty?
|
21
|
+
block ? sec.__send__(:_call, &block) : sec
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'section'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
module Features
|
7
|
+
# Creates frame-enclosed section with a highlighted `title` and
|
8
|
+
# prints given additional arguments as lines into the section.
|
9
|
+
#
|
10
|
+
# When no block is given, the section must be closed, see {Section#close}.
|
11
|
+
#
|
12
|
+
# @param [#to_s] title object to print as section title
|
13
|
+
# @param [Array<#to_s>] args more objects to print
|
14
|
+
# @param [Symbol] type frame type;
|
15
|
+
# valid types are `:rounded`, `:simple`, `:heavy`, `:semi`, `:double`
|
16
|
+
# @yieldparam [Wrapper::Framed] section the created section
|
17
|
+
# @return [Object] the result of the code block
|
18
|
+
# @return [Wrapper::Framed] itself, when no code block is given
|
19
|
+
def framed(title, *args, type: :rounded, &block)
|
20
|
+
_section(:Framed, args, title: title, type: type, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Wrapper
|
25
|
+
#
|
26
|
+
# A frame-enclosed {Section} with a highlighted title.
|
27
|
+
#
|
28
|
+
# @see Features#framed
|
29
|
+
class Framed < Section
|
30
|
+
protected
|
31
|
+
|
32
|
+
def initialize(parent, title:, type:, **opts)
|
33
|
+
top_start, top_suffix, left, bottom = components(type)
|
34
|
+
parent.puts(" #{title} ", prefix: top_start, suffix: top_suffix)
|
35
|
+
@bottom = bottom
|
36
|
+
super(parent, prefix: "#{left} ", **opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
def finish = parent.puts(@bottom)
|
40
|
+
|
41
|
+
def components(type)
|
42
|
+
COMPONENTS[type] || raise(ArgumentError, "invalid frame type - #{type}")
|
43
|
+
end
|
44
|
+
|
45
|
+
COMPONENTS = {
|
46
|
+
rounded: %w[╭── ───── │ ╰──────────],
|
47
|
+
simple: %w[┌── ───── │ └──────────],
|
48
|
+
heavy: %w[┏━━ ━━━━━ ┃ ┗━━━━━━━━━━],
|
49
|
+
semi: %w[┍━━ ━━━━━ │ ┕━━━━━━━━━━],
|
50
|
+
double: %w[╔══ ═════ ║ ╚══════════]
|
51
|
+
}.compare_by_identity.freeze
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'section'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
module Features
|
7
|
+
# Creates section with a H1 title.
|
8
|
+
#
|
9
|
+
# @param (see #information)
|
10
|
+
# @yieldparam [Wrapper::Heading] section the created section
|
11
|
+
# @return [Object] the result of the code block
|
12
|
+
# @return [Wrapper::Heading] itself, when no code block is given
|
13
|
+
def h1(title, *args, &block)
|
14
|
+
_section(:Heading, args, title: title, weight: 1, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates section with a H2 title.
|
18
|
+
#
|
19
|
+
# @param (see #information)
|
20
|
+
# @yieldparam (see #h1)
|
21
|
+
# @return (see #h1)
|
22
|
+
def h2(title, *args, &block)
|
23
|
+
_section(:Heading, args, title: title, weight: 2, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates section with a H3 title.
|
27
|
+
#
|
28
|
+
# @param (see #information)
|
29
|
+
# @yieldparam (see #h1)
|
30
|
+
# @return (see #h1)
|
31
|
+
def h3(title, *args, &block)
|
32
|
+
_section(:Heading, args, title: title, weight: 3, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates section with a H4 title.
|
36
|
+
#
|
37
|
+
# @param (see #information)
|
38
|
+
# @yieldparam (see #h1)
|
39
|
+
# @return (see #h1)
|
40
|
+
def h4(title, *args, &block)
|
41
|
+
_section(:Heading, args, title: title, weight: 4, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates section with a H5 title.
|
45
|
+
#
|
46
|
+
# @param (see #information)
|
47
|
+
# @yieldparam (see #h1)
|
48
|
+
# @return (see #h1)
|
49
|
+
def h5(title, *args, &block)
|
50
|
+
_section(:Heading, args, title: title, weight: 5, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Wrapper
|
55
|
+
#
|
56
|
+
# A {Section} with a highlighted title.
|
57
|
+
#
|
58
|
+
# @see Features#h1
|
59
|
+
# @see Features#h2
|
60
|
+
# @see Features#h3
|
61
|
+
# @see Features#h4
|
62
|
+
# @see Features#h5
|
63
|
+
class Heading < Section
|
64
|
+
protected
|
65
|
+
|
66
|
+
def initialize(parent, title:, weight:, **opts)
|
67
|
+
prefix, suffix = enclose(weight)
|
68
|
+
parent.puts(title, prefix: prefix, suffix: suffix)
|
69
|
+
super(parent, **opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
def enclose(weight)
|
73
|
+
enclose = ENCLOSE[weight]
|
74
|
+
return "#{enclose} ", " #{enclose}" if enclose
|
75
|
+
raise(ArgumentError, "invalid heading weight - #{weight}")
|
76
|
+
end
|
77
|
+
|
78
|
+
ENCLOSE = {
|
79
|
+
1 => '═══════',
|
80
|
+
2 => '━━━━━',
|
81
|
+
3 => '━━━',
|
82
|
+
4 => '───',
|
83
|
+
5 => '──'
|
84
|
+
}.compare_by_identity.freeze
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'section'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
module Features
|
7
|
+
# Creates a simple message section with a highlighted `title` and
|
8
|
+
# prints given additional arguments as lines into the section.
|
9
|
+
#
|
10
|
+
# @param [#to_s] title object to print as section title
|
11
|
+
# @param [Array<#to_s>] args more objects to print
|
12
|
+
# @param [#to_s] symbol symbol/prefix used for the title
|
13
|
+
# @yieldparam [Wrapper::Message] section the created section
|
14
|
+
# @return [Object] the result of the code block
|
15
|
+
# @return [Wrapper::Message] itself, when no code block is given
|
16
|
+
def message(title, *args, symbol: :default, &block)
|
17
|
+
_section(:Message, args, title: title, symbol: symbol, &block)
|
18
|
+
end
|
19
|
+
alias msg message
|
20
|
+
|
21
|
+
# Creates a informational message section with a highlighted `title` and
|
22
|
+
# prints given additional arguments as lines into the section.
|
23
|
+
#
|
24
|
+
# @param [#to_s] title object to print as section title
|
25
|
+
# @param [Array<#to_s>] args more objects to print
|
26
|
+
# @yieldparam (see #message)
|
27
|
+
# @return (see #message)
|
28
|
+
def information(title, *args, &block)
|
29
|
+
_section(:Message, args, title: title, symbol: :information, &block)
|
30
|
+
end
|
31
|
+
alias info information
|
32
|
+
|
33
|
+
# Creates a warning message section with a highlighted `title` and
|
34
|
+
# prints given additional arguments as lines into the section.
|
35
|
+
#
|
36
|
+
# @param (see #information)
|
37
|
+
# @yieldparam (see #message)
|
38
|
+
# @return (see #message)
|
39
|
+
def warning(title, *args, &block)
|
40
|
+
_section(:Message, args, title: title, symbol: :warning, &block)
|
41
|
+
end
|
42
|
+
alias warn warning
|
43
|
+
|
44
|
+
# Creates a error message section with a highlighted `title` and
|
45
|
+
# prints given additional arguments as lines into the section.
|
46
|
+
#
|
47
|
+
# @param (see #information)
|
48
|
+
# @yieldparam (see #message)
|
49
|
+
# @return (see #message)
|
50
|
+
def error(title, *args, &block)
|
51
|
+
_section(:Message, args, title: title, symbol: :error, &block)
|
52
|
+
end
|
53
|
+
alias err error
|
54
|
+
|
55
|
+
# Creates a completion message section with a highlighted `title` and
|
56
|
+
# prints given additional arguments as lines into the section.
|
57
|
+
#
|
58
|
+
# When used for a {#task} section it closes this section with status `:ok`.
|
59
|
+
#
|
60
|
+
# @param (see #information)
|
61
|
+
# @yieldparam (see #message)
|
62
|
+
# @return (see #message)
|
63
|
+
def completed(title, *args, &block)
|
64
|
+
_section(:Message, args, title: title, symbol: :completed, &block)
|
65
|
+
end
|
66
|
+
alias done completed
|
67
|
+
alias ok completed
|
68
|
+
|
69
|
+
# Creates a failure message section with a highlighted `title` and
|
70
|
+
# prints given additional arguments as lines into the section.
|
71
|
+
#
|
72
|
+
# When used for a {#task} section it closes this section with status
|
73
|
+
# `:failed`.
|
74
|
+
#
|
75
|
+
# @param (see #information)
|
76
|
+
# @yieldparam (see #message)
|
77
|
+
# @return (see #message)
|
78
|
+
def failed(title, *args, &block)
|
79
|
+
_section(:Message, args, title: title, symbol: :failed, &block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Wrapper
|
84
|
+
#
|
85
|
+
# A {Section} with a highlighted title.
|
86
|
+
#
|
87
|
+
# @see Features#message
|
88
|
+
# @see Features#information
|
89
|
+
# @see Features#warning
|
90
|
+
# @see Features#error
|
91
|
+
# @see Features#completed
|
92
|
+
# @see Features#failed
|
93
|
+
class Message < Section
|
94
|
+
protected
|
95
|
+
|
96
|
+
def initialize(parent, title:, symbol:, **opts)
|
97
|
+
parent.puts(title, **title_attr(str = as_symbol_str(symbol), symbol))
|
98
|
+
super(parent, prefix: ' ' * (NattyUI.display_width(str) + 1), **opts)
|
99
|
+
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
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NattyUI
|
4
|
+
#
|
5
|
+
# Additional attributes for progression elements.
|
6
|
+
#
|
7
|
+
# Progression elements have additional states ({#completed?}, {#failed?}) and
|
8
|
+
# can be closed with calling {Features#completed} or {Features#failed}.
|
9
|
+
#
|
10
|
+
# @see Wrapper::Progress
|
11
|
+
# @see Wrapper::Task
|
12
|
+
#
|
13
|
+
module ProgressAttributes
|
14
|
+
# @attribute [r] completed?
|
15
|
+
# @return [Boolean] whether the task completed sucessfully
|
16
|
+
def completed? = (@status == :completed)
|
17
|
+
|
18
|
+
# @attribute [r] failed?
|
19
|
+
# @return [Boolean] whether the task failed
|
20
|
+
def failed? = (@status == :failed)
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def completed(*args)
|
24
|
+
@final_text = args unless args.empty?
|
25
|
+
_close(:completed)
|
26
|
+
end
|
27
|
+
alias done completed
|
28
|
+
alias ok completed
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
def failed(*args)
|
32
|
+
@final_text = args unless args.empty?
|
33
|
+
_close(:failed)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Additional attributes for progression elements.
|
39
|
+
#
|
40
|
+
# @see Wrapper::Progress
|
41
|
+
#
|
42
|
+
module ValueAttributes
|
43
|
+
# @return [Float] current value
|
44
|
+
attr_reader :value
|
45
|
+
|
46
|
+
def value=(val)
|
47
|
+
@value = [0, val.to_f].max
|
48
|
+
@max_value = @value if @max_value&.< 0
|
49
|
+
redraw
|
50
|
+
end
|
51
|
+
|
52
|
+
# Maximal value.
|
53
|
+
#
|
54
|
+
# @return [Float] maximal value
|
55
|
+
# @return [nil] when no max_value was configured
|
56
|
+
attr_reader :max_value
|
57
|
+
|
58
|
+
# Increase the value by given amount.
|
59
|
+
#
|
60
|
+
# @param increment [#to_f] value increment
|
61
|
+
# @return [Wrapper::Element] itself
|
62
|
+
def step(increment = 1)
|
63
|
+
self.value = @value + increment.to_f
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'element'
|
4
|
+
require_relative 'mixins'
|
5
|
+
|
6
|
+
module NattyUI
|
7
|
+
module Features
|
8
|
+
# Creates progress element implementing additional {ProgressAttributes}.
|
9
|
+
#
|
10
|
+
# A progress element has additional states and can be closed with {#completed}
|
11
|
+
# or {#failed}.
|
12
|
+
#
|
13
|
+
# @param [#to_s] title object to print as progress title
|
14
|
+
# @param [##to_f] max_value maximum value of the progress
|
15
|
+
# @return [Wrapper::Progress] the created progress element
|
16
|
+
def progress(title, max_value: nil)
|
17
|
+
_section(:Progress, nil, title: title, max_value: max_value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Wrapper
|
22
|
+
#
|
23
|
+
# An {Element} displaying a progression.
|
24
|
+
#
|
25
|
+
# @see Features#progress
|
26
|
+
class Progress < Element
|
27
|
+
include ProgressAttributes
|
28
|
+
include ValueAttributes
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def initialize(parent, title:, max_value:, **_)
|
33
|
+
super(parent)
|
34
|
+
@final_text = [title]
|
35
|
+
@max_value = [0, max_value.to_f].max if max_value
|
36
|
+
@value = 0
|
37
|
+
@progress = 0
|
38
|
+
draw_title(title)
|
39
|
+
end
|
40
|
+
|
41
|
+
def draw_title(title) = (wrapper.stream << prefix << "➔ #{title} ").flush
|
42
|
+
def draw_final = (wrapper.stream << "\n")
|
43
|
+
|
44
|
+
def redraw
|
45
|
+
return (wrapper.stream << '.').flush unless @max_value
|
46
|
+
cn = (20 * @value / @max_value).to_i
|
47
|
+
return if @progress == cn
|
48
|
+
(wrapper.stream << ('.' * (cn - @progress))).flush
|
49
|
+
@progress = cn
|
50
|
+
end
|
51
|
+
|
52
|
+
def finish
|
53
|
+
draw_final
|
54
|
+
return @parent.failed(*@final_text) if failed?
|
55
|
+
@status = :ok if @status == :closed
|
56
|
+
@parent.completed(*@final_text)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'element'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
module Features
|
7
|
+
# Request a choice from user.
|
8
|
+
#
|
9
|
+
# @example Select by Index
|
10
|
+
# choice = sec.query(
|
11
|
+
# 'Which fruits do you prefer?',
|
12
|
+
# 'Apples',
|
13
|
+
# 'Bananas',
|
14
|
+
# 'Cherries'
|
15
|
+
# )
|
16
|
+
# # => '1' or '2' or '3' or nil if user aborted
|
17
|
+
#
|
18
|
+
# @example Select by given char
|
19
|
+
# choice = sec.query(
|
20
|
+
# 'Which fruits do you prefer?',
|
21
|
+
# a: 'Apples',
|
22
|
+
# b: 'Bananas',
|
23
|
+
# c: 'Cherries'
|
24
|
+
# )
|
25
|
+
# # => 'a' or 'b' or 'c' or nil if user aborted
|
26
|
+
#
|
27
|
+
# @param question [#to_s] Question to display
|
28
|
+
# @param choices [#to_s] choices selectable via index (0..9)
|
29
|
+
# @param result [Symbol] defines how the result ist returned
|
30
|
+
# @param kw_choices [{Char => #to_s}] choices selectable with given char
|
31
|
+
# @return [Char] when `result` is configured as `:char`
|
32
|
+
# @return [#to_s] when `result` is configured as `:choice`
|
33
|
+
# @return [[Char, #to_s]] when `result` is configured as `:both`
|
34
|
+
# @return [nil] when input was aborted with `ESC`, `^C` or `^D`
|
35
|
+
def query(question, *choices, result: :char, **kw_choices)
|
36
|
+
_element(:Query, question, choices, kw_choices, result)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Wrapper
|
41
|
+
#
|
42
|
+
# An {Element} to request a user choice.
|
43
|
+
#
|
44
|
+
# @see Features#query
|
45
|
+
class Query < Element
|
46
|
+
protected
|
47
|
+
|
48
|
+
def _call(question, choices, kw_choices, result_typye)
|
49
|
+
choices = grab(choices, kw_choices)
|
50
|
+
return if choices.empty?
|
51
|
+
wrapper.temporary do
|
52
|
+
__section(
|
53
|
+
@parent,
|
54
|
+
:Message,
|
55
|
+
choices.map { |k, v| "#{k} #{v}" },
|
56
|
+
title: question,
|
57
|
+
symbol: :query
|
58
|
+
)
|
59
|
+
read(choices, result_typye)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def read(choices, result_typye)
|
64
|
+
while true
|
65
|
+
char = NattyUI.in_stream.getch
|
66
|
+
return if "\3\4\e".include?(char)
|
67
|
+
next unless choices.key?(char)
|
68
|
+
return char if result_typye == :char
|
69
|
+
return choices[char] if result_typye == :choice
|
70
|
+
return char, choices[char]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def grab(choices, kw_choices)
|
75
|
+
Array
|
76
|
+
.new(choices.size) { |i| i + 1 }
|
77
|
+
.zip(choices)
|
78
|
+
.to_h
|
79
|
+
.merge!(kw_choices)
|
80
|
+
.transform_keys! { |k| [k.to_s[0], ' '].max }
|
81
|
+
.transform_values! { |v| v.to_s.tr("\r\n", ' ') }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'element'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
module Features
|
7
|
+
# Creates a default section and prints given arguments as lines
|
8
|
+
# into the section.
|
9
|
+
#
|
10
|
+
# @param [Array<#to_s>] args objects to print
|
11
|
+
# @yieldparam [Wrapper::Section] section the created section
|
12
|
+
# @return [Object] the result of the code block
|
13
|
+
# @return [Wrapper::Section] itself, when no code block is given
|
14
|
+
def section(*args, &block)
|
15
|
+
_section(:Section, args, prefix: ' ', suffix: ' ', &block)
|
16
|
+
end
|
17
|
+
alias sec section
|
18
|
+
|
19
|
+
# Creates a quotation section and prints given arguments as lines
|
20
|
+
# into the section.
|
21
|
+
#
|
22
|
+
# @param (see #section)
|
23
|
+
# @yieldparam (see #section)
|
24
|
+
# @return (see #section)
|
25
|
+
def quote(*args, &block)
|
26
|
+
_section(:Section, args, prefix: '▍ ', prefix_attr: 39, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Wrapper
|
31
|
+
#
|
32
|
+
# Visual element to keep text lines together.
|
33
|
+
#
|
34
|
+
# A section can contain other elements and sections.
|
35
|
+
#
|
36
|
+
# @see Features#section
|
37
|
+
# @see Features#quote
|
38
|
+
class Section < Element
|
39
|
+
# Close the section.
|
40
|
+
#
|
41
|
+
# @return [Section] itself when used without a code block
|
42
|
+
# @return [nil] when used with a code block
|
43
|
+
def close = _close(:closed)
|
44
|
+
|
45
|
+
# Print given arguments as lines into the section.
|
46
|
+
#
|
47
|
+
# @overload puts(...)
|
48
|
+
# @param [#to_s] ... objects to print
|
49
|
+
# @comment @param [#to_s, nil] prefix line prefix
|
50
|
+
# @comment @param [#to_s, nil] suffix line suffix
|
51
|
+
# @return [Section] itself
|
52
|
+
def puts(*args, prefix: nil, suffix: nil)
|
53
|
+
return self if @status
|
54
|
+
@parent.puts(
|
55
|
+
*args,
|
56
|
+
prefix: prefix ? "#{@prefix}#{prefix}" : @prefix,
|
57
|
+
suffix: suffix ? "#{@suffix}#{suffix}" : @suffix
|
58
|
+
)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
alias add puts
|
62
|
+
|
63
|
+
# Add at least one empty line
|
64
|
+
#
|
65
|
+
# @param [#to_i] lines count of lines
|
66
|
+
# @return [Section] itself
|
67
|
+
def space(lines = 1)
|
68
|
+
@parent.puts(
|
69
|
+
*Array.new([lines.to_i, 1].max),
|
70
|
+
prefix: @prefix,
|
71
|
+
suffix: @suffix
|
72
|
+
)
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# @note The screen manipulation is only available in ANSI mode see {#ansi?}
|
77
|
+
#
|
78
|
+
# Resets the part of the screen written below the current output line when
|
79
|
+
# the given block ended.
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
# section.temporary do |temp|
|
83
|
+
# temp.info('This message will disappear in 5 seconds!')
|
84
|
+
# sleep 5
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# @yield [Section] itself
|
88
|
+
# @return [Object] block result
|
89
|
+
def temporary
|
90
|
+
block_given? ? yield(self) : self
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
def initialize(parent, prefix: nil, suffix: nil, **_)
|
96
|
+
super(parent)
|
97
|
+
@prefix = prefix
|
98
|
+
@suffix = suffix
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|