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.
@@ -1,87 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'section'
3
+ require_relative 'element'
4
4
 
5
5
  module NattyUI
6
6
  module Features
7
- # Creates section with a H1 title.
7
+ # Prints a H1 title.
8
8
  #
9
- # @param (see #information)
10
- # @yieldparam [Wrapper::Heading] heading 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(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
- # Creates section with a H2 title.
13
+ # Prints a H2 title.
18
14
  #
19
- # @param (see #information)
20
- # @yieldparam (see #h1)
15
+ # @param (see #h1)
21
16
  # @return (see #h1)
22
- def h2(title, *args, &block)
23
- _section(self, :Heading, args, title: title, weight: 2, &block)
24
- end
17
+ def h2(title) = _element(:Heading, title, '━━━━━')
25
18
 
26
- # Creates section with a H3 title.
19
+ # Prints a H3 title.
27
20
  #
28
- # @param (see #information)
29
- # @yieldparam (see #h1)
21
+ # @param (see #h1)
30
22
  # @return (see #h1)
31
- def h3(title, *args, &block)
32
- _section(self, :Heading, args, title: title, weight: 3, &block)
33
- end
23
+ def h3(title) = _element(:Heading, title, '━━━')
34
24
 
35
- # Creates section with a H4 title.
25
+ # Prints a H4 title.
36
26
  #
37
- # @param (see #information)
38
- # @yieldparam (see #h1)
27
+ # @param (see #h1)
39
28
  # @return (see #h1)
40
- def h4(title, *args, &block)
41
- _section(self, :Heading, args, title: title, weight: 4, &block)
42
- end
29
+ def h4(title) = _element(:Heading, title, '───')
43
30
 
44
- # Creates section with a H5 title.
31
+ # Prints a H5 title.
45
32
  #
46
- # @param (see #information)
47
- # @yieldparam (see #h1)
33
+ # @param (see #h1)
48
34
  # @return (see #h1)
49
- def h5(title, *args, &block)
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 {Section} with a highlighted title.
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 < Section
47
+ class Heading < Element
64
48
  protected
65
49
 
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}")
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) = _element(:ListInColumns, args, compact)
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 _call(list, compact)
56
+ def call(list, compact, glyph)
57
+ return @parent if list.empty?
29
58
  list.flatten!
30
- return parent if list.empty?
31
- list.map! { |item| Item.new(item = item.to_s, _plain_width(item)) }
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) { |line| parent.puts(line) }
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
- each(list, available_width) { |line| parent.puts(line) }
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 { |item| item.to_s(width) }.join)
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 { |ary| ary.max_by(&:width).width + 3 }
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
- Data.define(:str, :width) do
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 simple message section with a highlighted `title` and
8
- # prints given additional arguments as lines into the section.
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] symbol symbol/prefix used for the title
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, symbol: :default, &block)
17
- _section(self, :Message, args, title: title, symbol: symbol, &block)
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(self, :Message, args, title: title, symbol: :information, &block)
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(self, :Message, args, title: title, symbol: :warning, &block)
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(self, :Message, args, title: title, symbol: :error, &block)
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(self, :Message, args, title: title, symbol: :completed, &block)
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(self, :Message, args, title: title, symbol: :failed, &block)
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:, symbol:, **opts)
97
- parent.puts(title, **title_attr(str = as_symbol_str(symbol), symbol))
98
- super(parent, prefix: ' ' * (NattyUI.display_width(str) + 1), **opts)
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
@@ -49,9 +49,9 @@ module NattyUI
49
49
  redraw
50
50
  end
51
51
 
52
- # Maximal value.
52
+ # Maximum value.
53
53
  #
54
- # @return [Float] maximal value
54
+ # @return [Float] maximum value
55
55
  # @return [nil] when no max_value was configured
56
56
  attr_reader :max_value
57
57
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'element'
4
- require_relative '../mixins'
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
- _section(self, :Progress, nil, title: title, max_value: max_value)
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 initialize(parent, title:, max_value:, **_)
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
- symbol: @status = :completed
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 = sec.query(
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 = sec.query(
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 `ESC`, `^C` or `^D`
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 _call(question, choices, kw_choices, result_typye)
51
- choices = grab(choices, kw_choices)
52
- return if choices.empty?
53
- wrapper.temporary do
54
- _section(
55
- @parent,
56
- :Message,
57
- choices.map { |k, v| "#{k} #{v}" },
58
- title: question,
59
- symbol: :query
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 read(choices, result_typye)
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\e".include?(char)
80
+ return if "\3\4".include?(char)
69
81
  next unless choices.key?(char)
70
- return char if result_typye == :char
71
- return choices[char] if result_typye == :choice
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
- def grab(choices, kw_choices)
77
- Array
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) = _element(: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 _call(question)
24
- NattyUI.readline(prompt(question), stream: wrapper.stream)
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
- finish
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
- def prompt(question) = "#{prefix}▶︎ #{question}: "
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