natty-ui 0.7.0 → 0.9.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +7 -3
  3. data/README.md +25 -47
  4. data/examples/24bit-colors.rb +27 -0
  5. data/examples/3bit-colors.rb +14 -0
  6. data/examples/8bit-colors.rb +31 -0
  7. data/examples/animate.rb +24 -0
  8. data/examples/attributes.rb +25 -159
  9. data/examples/demo.rb +53 -0
  10. data/examples/illustration.png +0 -0
  11. data/examples/illustration.rb +29 -0
  12. data/examples/{list_in_columns.rb → ls.rb} +13 -20
  13. data/examples/message.rb +30 -0
  14. data/examples/progress.rb +25 -30
  15. data/examples/query.rb +26 -28
  16. data/examples/read_key.rb +13 -0
  17. data/examples/table.rb +36 -0
  18. data/lib/natty-ui/ansi.rb +351 -305
  19. data/lib/natty-ui/ansi_constants.rb +73 -0
  20. data/lib/natty-ui/ansi_wrapper.rb +136 -162
  21. data/lib/natty-ui/features.rb +11 -18
  22. data/lib/natty-ui/key_map.rb +119 -0
  23. data/lib/natty-ui/line_animation/default.rb +35 -0
  24. data/lib/natty-ui/line_animation/matrix.rb +28 -0
  25. data/lib/natty-ui/line_animation/rainbow.rb +30 -0
  26. data/lib/natty-ui/line_animation/test.rb +29 -0
  27. data/lib/natty-ui/line_animation/type_writer.rb +64 -0
  28. data/lib/natty-ui/line_animation.rb +54 -0
  29. data/lib/natty-ui/version.rb +2 -2
  30. data/lib/natty-ui/wrapper/animate.rb +17 -0
  31. data/lib/natty-ui/wrapper/ask.rb +21 -21
  32. data/lib/natty-ui/wrapper/element.rb +19 -23
  33. data/lib/natty-ui/wrapper/framed.rb +29 -19
  34. data/lib/natty-ui/wrapper/heading.rb +26 -53
  35. data/lib/natty-ui/wrapper/horizontal_rule.rb +37 -0
  36. data/lib/natty-ui/wrapper/list_in_columns.rb +71 -12
  37. data/lib/natty-ui/wrapper/message.rb +20 -27
  38. data/lib/natty-ui/wrapper/progress.rb +40 -13
  39. data/lib/natty-ui/wrapper/query.rb +34 -31
  40. data/lib/natty-ui/wrapper/quote.rb +25 -0
  41. data/lib/natty-ui/wrapper/request.rb +21 -10
  42. data/lib/natty-ui/wrapper/section.rb +55 -39
  43. data/lib/natty-ui/wrapper/table.rb +298 -0
  44. data/lib/natty-ui/wrapper/task.rb +6 -7
  45. data/lib/natty-ui/wrapper.rb +123 -41
  46. data/lib/natty-ui.rb +65 -40
  47. metadata +28 -9
  48. data/examples/basic.rb +0 -62
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module LineAnimation
5
+ class Rainbow < None
6
+ def initialize(*_)
7
+ super
8
+ @prefix = to_column
9
+ end
10
+
11
+ def print(line)
12
+ line = plain(line)
13
+ 11.upto(200) do |spread|
14
+ (
15
+ @stream << @prefix <<
16
+ Ansi.rainbow(
17
+ line,
18
+ frequence: 0.1,
19
+ spread: spread / 100.0,
20
+ seed: 0
21
+ )
22
+ ).flush
23
+ sleep(0.01)
24
+ end
25
+ end
26
+ end
27
+
28
+ define rainbow: Rainbow
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module LineAnimation
5
+ class Test < None
6
+ def initialize(*_)
7
+ super
8
+ @color = attribute(:color, :default)
9
+ end
10
+
11
+ def print(line, column)
12
+ prefix = "#{Ansi.cursor_column(column)}#{@color}"
13
+ line = plain(line)
14
+ str = Array.new(line.size) { CHARS.sample }.join
15
+ pos = Array.new(line.size, &:itself).shuffle
16
+ until pos.size < 4
17
+ pos.shift(pos.size / 4).each { str[_1] = line[_1] }
18
+ pos.sample(pos.size / 2).each { str[_1] = CHARS.sample }
19
+ (@stream << "#{prefix}#{str}").flush
20
+ sleep(0.08)
21
+ end
22
+ end
23
+
24
+ CHARS = '2598Z*):.\=+-¦|_ヲアウエオカキケコサシスセソタツテナニヌネハヒホマミムメモヤユラリワ'.chars
25
+ end
26
+
27
+ define test: Test
28
+ end
29
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module LineAnimation
5
+ class TypeWriter < None
6
+ def initialize(*_)
7
+ super
8
+ @color = color
9
+ @cursor_color = attribute(:cursor_color, 0x2e)
10
+ @column = @options[:prefix_width] + 1
11
+ @num = 0
12
+ end
13
+
14
+ def print(line)
15
+ line = plain(line)
16
+ if (@num += 1).odd?
17
+ line.each_char do |char|
18
+ cursor(char)
19
+ (@stream << char).flush
20
+ end
21
+ else
22
+ pos = @column + line.size
23
+ line.reverse!
24
+ line.each_char do |char|
25
+ @stream << Ansi.cursor_column(pos -= 1)
26
+ cursor(char)
27
+ (@stream << char).flush
28
+ end
29
+ end
30
+ end
31
+
32
+ def cursor(char)
33
+ return sleep(0.016) if SPACE.match?(char)
34
+ @stream << @cursor_color
35
+ '▁▂▃▄▅▆▇█'.each_char do |cursor|
36
+ (@stream << cursor).flush
37
+ sleep(0.002)
38
+ @stream << CURSOR_BACK
39
+ end
40
+ @stream << @color
41
+ end
42
+
43
+ def print_org(line)
44
+ plain(line).each_char do |char|
45
+ if SPACE.match?(char)
46
+ sleep(0.016)
47
+ else
48
+ @stream << @cursor_color
49
+ '▁▂▃▄▅▆▇█'.each_char do |cursor|
50
+ (@stream << cursor).flush
51
+ sleep(0.002)
52
+ @stream << CURSOR_BACK
53
+ end
54
+ end
55
+ (@stream << @color << char).flush
56
+ end
57
+ end
58
+
59
+ CURSOR_BACK = Ansi.cursor_back(1)
60
+ end
61
+
62
+ define type_writer: TypeWriter
63
+ end
64
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ansi'
4
+
5
+ module NattyUI
6
+ module LineAnimation
7
+ def self.defined = @defined.keys
8
+ def self.defined?(name) = @defined.key?(name)
9
+ def self.define(**kwargs) = @defined.merge!(kwargs)
10
+
11
+ def self.[](name)
12
+ return if name.nil?
13
+ klass = @defined[name] || @defined[:default]
14
+ return klass unless klass.is_a?(String)
15
+ require(klass)
16
+ klass = @defined[name] and return klass
17
+ raise(LoadError, "unknown animation - #{name}")
18
+ end
19
+
20
+ class None
21
+ def initialize(stream, options)
22
+ @stream = stream
23
+ @options = options
24
+ end
25
+
26
+ def print(_line) = nil
27
+
28
+ protected
29
+
30
+ def to_column = Ansi.cursor_column(@options[:prefix_width] + 1)
31
+ def color = attribute(:color, :default)
32
+ def plain(str) = NattyUI.plain(str, ansi: false)
33
+
34
+ def attribute(name, *default)
35
+ att = @options[name] or return Ansi[*default]
36
+ return Ansi[*att] if att.is_a?(Enumerable)
37
+ Ansi.try_convert(att.to_s) || Ansi[*default]
38
+ end
39
+
40
+ SPACE = /[[:space:]]/
41
+ end
42
+
43
+ dir = __dir__
44
+ @defined = {
45
+ default: "#{dir}/line_animation/default",
46
+ matrix: "#{dir}/line_animation/matrix",
47
+ rainbow: "#{dir}/line_animation/rainbow",
48
+ type_writer: "#{dir}/line_animation/type_writer",
49
+ test: "#{dir}/line_animation/test"
50
+ }.compare_by_identity
51
+ end
52
+
53
+ private_constant :LineAnimation
54
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NattyUI
4
- # @return [String] the version number of the gem
5
- VERSION = '0.7.0'
4
+ # The version number of the gem.
5
+ VERSION = '0.9.0'
6
6
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module Features
5
+ # Print given arguments line-wise with animation.
6
+ #
7
+ # @overload animate(..., animation: :default)
8
+ # @param [#to_s] ... objects to print
9
+ # @param [:default, :matrix, :rainbow, :type_writer]
10
+ # animation type of animation
11
+ # @return [Wrapper::Section, Wrapper] it's parent object
12
+ def animate(*args, **kwargs)
13
+ kwargs[:animation] ||= :default
14
+ puts(*args, **kwargs)
15
+ end
16
+ end
17
+ end
@@ -13,13 +13,13 @@ module NattyUI
13
13
  # The default for `yes` includes `ENTER` and `RETURN` key
14
14
  #
15
15
  # @example
16
- # case sec.ask('Do you like the NattyUI gem?')
16
+ # case ui.ask('Do you like the NattyUI gem?')
17
17
  # when true
18
- # sec.info('Yeah!!')
18
+ # ui.info('Yeah!!')
19
19
  # when false
20
- # sec.write("That's pity!")
20
+ # ui.write("That's pity!")
21
21
  # else
22
- # sec.failed('You should have an opinion!')
22
+ # ui.failed('You should have an opinion!')
23
23
  # end
24
24
  #
25
25
  # @see NattyUI.in_stream
@@ -28,7 +28,7 @@ module NattyUI
28
28
  # @param yes [#to_s] chars which will be used to answer 'Yes'
29
29
  # @param no [#to_s] chars which will be used to answer 'No'
30
30
  # @return [Boolean] whether the answer is yes or no
31
- # @return [nil] when input was aborted with `ESC`, `^C` or `^D`
31
+ # @return [nil] when input was aborted with `^C` or `^D`
32
32
  def ask(question, yes: "jotsyd\r\n", no: 'n')
33
33
  _element(:Ask, question, yes, no)
34
34
  end
@@ -42,27 +42,27 @@ module NattyUI
42
42
  class Ask < Element
43
43
  protected
44
44
 
45
- def _call(question, yes, no)
45
+ def call(question, yes, no)
46
46
  yes, no = grab(yes, no)
47
- query(question)
48
- read(yes, no)
49
- ensure
50
- finish
51
- end
52
-
53
- def query(question)
54
- (wrapper.stream << prefix << "▶︎ #{question} ").flush
55
- end
56
-
57
- def finish = (wrapper.stream << "\n").flush
58
-
59
- def read(yes, no)
47
+ draw(question)
60
48
  while true
61
- char = NattyUI.in_stream.getch
62
- return if "\3\4\e".include?(char)
49
+ char = NattyUI.read_key(mode: :raw)
50
+ return if "\3\4".include?(char)
63
51
  return true if yes.include?(char)
64
52
  return false if no.include?(char)
65
53
  end
54
+ ensure
55
+ wrapper.ansi? ? (wrapper.stream << Ansi::CLL).flush : @parent.puts
56
+ end
57
+
58
+ def draw(question)
59
+ glyph = wrapper.glyph(:query)
60
+ @parent.print(
61
+ question,
62
+ prefix: "#{glyph} #{Ansi[255]}",
63
+ prefix_width: NattyUI.display_width(glyph) + 1,
64
+ suffix_width: 0
65
+ )
66
66
  end
67
67
 
68
68
  def grab(yes, no)
@@ -10,7 +10,7 @@ module NattyUI
10
10
  class Element
11
11
  include Features
12
12
 
13
- # @return [Section] when embedded in a section
13
+ # @return [Element] when embedded in a section
14
14
  # @return [Wrapper] when not embedded in a section
15
15
  attr_reader :parent
16
16
 
@@ -22,6 +22,9 @@ module NattyUI
22
22
  # @return [Boolean] whether its closed or not
23
23
  def closed? = (@status != nil)
24
24
 
25
+ # @return [Integer] available columns count within the element
26
+ def available_width = @parent.available_width
27
+
25
28
  # Close the element.
26
29
  #
27
30
  # @return [Element] itself
@@ -29,28 +32,11 @@ module NattyUI
29
32
 
30
33
  alias _to_s to_s
31
34
  private :_to_s
32
-
33
35
  # @!visibility private
34
- def inspect = "#{_to_s[..-2]} status=#{@status}}}>"
36
+ def inspect = _to_s
35
37
 
36
38
  protected
37
39
 
38
- def prefix = "#{@parent.__send__(:prefix)}#{@prefix}"
39
- def suffix = "#{@suffix}#{@parent.__send__(:suffix)}"
40
- def prefix_width = _blemish_width(prefix)
41
- def suffix_width = _blemish_width(suffix)
42
- def available_width = wrapper.screen_columns - prefix_width - suffix_width
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, **_) = (@parent = parent)
53
-
54
40
  def _close(state)
55
41
  return self if @status
56
42
  @status = state
@@ -58,18 +44,28 @@ module NattyUI
58
44
  @raise ? raise(BREAK) : self
59
45
  end
60
46
 
61
- def _call
47
+ def call
48
+ NattyUI.instance_variable_set(:@element, self)
62
49
  @raise = true
63
50
  yield(self)
64
- close unless closed?
51
+ closed? ? self : close
65
52
  rescue BREAK
66
53
  nil
54
+ ensure
55
+ NattyUI.instance_variable_set(:@element, @parent)
67
56
  end
68
57
 
69
- BREAK = Class.new(StandardError)
70
- private_constant :BREAK
58
+ def finish = nil
59
+ def prefix = "#{@parent.instance_variable_get(:@prefix)}#{@prefix}"
60
+
61
+ def initialize(parent)
62
+ @parent = parent
63
+ end
71
64
 
72
65
  private_class_method :new
66
+
67
+ BREAK = Class.new(StandardError)
68
+ private_constant :BREAK
73
69
  end
74
70
  end
75
71
  end
@@ -10,15 +10,13 @@ module NattyUI
10
10
  # When no block is given, the section must be closed, see
11
11
  # {Wrapper::Element#close}.
12
12
  #
13
- # @param [#to_s] title object to print as section title
14
13
  # @param [Array<#to_s>] args more objects to print
15
- # @param [Symbol] type frame type;
16
- # valid types are `:rounded`, `:simple`, `:heavy`, `:semi`, `:double`
14
+ # @param [:block, :double, :heavy, :rounded, :semi, :simple] type frame type
17
15
  # @yieldparam [Wrapper::Framed] framed the created section
18
16
  # @return [Object] the result of the code block
19
17
  # @return [Wrapper::Framed] itself, when no code block is given
20
- def framed(title, *args, type: :rounded, &block)
21
- _section(self, :Framed, args, title: title, type: type, &block)
18
+ def framed(*args, type: :rounded, &block)
19
+ _section(:Framed, args, type: type, &block)
22
20
  end
23
21
  end
24
22
 
@@ -30,25 +28,37 @@ module NattyUI
30
28
  class Framed < Section
31
29
  protected
32
30
 
33
- def initialize(parent, title:, type:, **opts)
34
- top_start, top_suffix, left, bottom = components(type)
35
- parent.puts(" #{title} ", prefix: top_start, suffix: top_suffix)
36
- @bottom = bottom
37
- super(parent, prefix: "#{left} ", **opts)
31
+ def initialize(parent, type:)
32
+ deco = as_deco(type)
33
+ super(parent, prefix: "#{deco[0]} ", prefix_width: 2, suffix_width: 2)
34
+ init(deco)
38
35
  end
39
36
 
40
- def finish = parent.puts(@bottom)
37
+ def as_deco(type)
38
+ if type.is_a?(Symbol)
39
+ ret = DECO[type] and return ret
40
+ elsif type.is_a?(String)
41
+ return type if type.size == 8
42
+ return type * 8 if type.size == 1
43
+ end
44
+ raise(ArgumentError, "invalid frame type - #{type.inspect}")
45
+ end
41
46
 
42
- def components(type)
43
- COMPONENTS[type] || raise(ArgumentError, "invalid frame type - #{type}")
47
+ def init(deco)
48
+ aw = @parent.available_width - 1
49
+ parent.puts("#{deco[1]}#{deco[2] * aw}")
50
+ @finish = "#{deco[5]}#{deco[6] * aw}"
44
51
  end
45
52
 
46
- COMPONENTS = {
47
- rounded: %w[╭── ───── │ ╰──────────],
48
- simple: %w[┌── ───── │ └──────────],
49
- heavy: %w[┏━━ ━━━━━ ┃ ┗━━━━━━━━━━],
50
- semi: %w[┍━━ ━━━━━ │ ┕━━━━━━━━━━],
51
- double: %w[╔══ ═════ ║ ╚══════════]
53
+ def finish = @parent.puts(@finish)
54
+
55
+ DECO = {
56
+ rounded: '│╭─╮│╰─╯',
57
+ simple: '│┌─┐│└─┘',
58
+ heavy: '┃┏━┓┃┗━┛',
59
+ double: '║╔═╗║╚═╝',
60
+ semi: '│╒═╕│╘═╛',
61
+ block: '▌▛▀▜▐▙▄▟'
52
62
  }.compare_by_identity.freeze
53
63
  end
54
64
  end
@@ -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[:bold_off, 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.print(
27
+ symbol * ((max_width / size)),
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