natty-ui 0.12.1 → 0.25.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +22 -26
  4. data/examples/24bit-colors.rb +4 -7
  5. data/examples/3bit-colors.rb +28 -6
  6. data/examples/8bit-colors.rb +18 -21
  7. data/examples/attributes.rb +30 -22
  8. data/examples/cols.rb +40 -0
  9. data/examples/elements.rb +31 -0
  10. data/examples/examples.rb +45 -0
  11. data/examples/illustration.rb +56 -54
  12. data/examples/ls.rb +16 -16
  13. data/examples/named-colors.rb +23 -0
  14. data/examples/sections.rb +29 -0
  15. data/examples/tables.rb +62 -0
  16. data/examples/tasks.rb +52 -0
  17. data/lib/natty-ui/attributes.rb +604 -0
  18. data/lib/natty-ui/choice.rb +56 -0
  19. data/lib/natty-ui/dumb_choice.rb +45 -0
  20. data/lib/natty-ui/element.rb +78 -0
  21. data/lib/natty-ui/features.rb +798 -0
  22. data/lib/natty-ui/framed.rb +51 -0
  23. data/lib/natty-ui/ls_renderer.rb +93 -0
  24. data/lib/natty-ui/progress.rb +187 -0
  25. data/lib/natty-ui/section.rb +69 -0
  26. data/lib/natty-ui/table.rb +241 -0
  27. data/lib/natty-ui/table_renderer.rb +147 -0
  28. data/lib/natty-ui/task.rb +44 -0
  29. data/lib/natty-ui/temporary.rb +38 -0
  30. data/lib/natty-ui/theme.rb +303 -0
  31. data/lib/natty-ui/utils.rb +79 -0
  32. data/lib/natty-ui/version.rb +1 -1
  33. data/lib/natty-ui/width_finder.rb +125 -0
  34. data/lib/natty-ui.rb +89 -147
  35. metadata +44 -53
  36. data/examples/animate.rb +0 -42
  37. data/examples/attributes_list.rb +0 -12
  38. data/examples/demo.rb +0 -51
  39. data/examples/message.rb +0 -30
  40. data/examples/progress.rb +0 -66
  41. data/examples/query.rb +0 -39
  42. data/examples/read_key.rb +0 -13
  43. data/examples/table.rb +0 -39
  44. data/lib/natty-ui/animation/binary.rb +0 -36
  45. data/lib/natty-ui/animation/default.rb +0 -38
  46. data/lib/natty-ui/animation/matrix.rb +0 -51
  47. data/lib/natty-ui/animation/rainbow.rb +0 -28
  48. data/lib/natty-ui/animation/type_writer.rb +0 -44
  49. data/lib/natty-ui/animation.rb +0 -69
  50. data/lib/natty-ui/ansi/constants.rb +0 -75
  51. data/lib/natty-ui/ansi.rb +0 -530
  52. data/lib/natty-ui/ansi_wrapper.rb +0 -232
  53. data/lib/natty-ui/frame.rb +0 -53
  54. data/lib/natty-ui/glyph.rb +0 -64
  55. data/lib/natty-ui/key_map.rb +0 -142
  56. data/lib/natty-ui/preload.rb +0 -12
  57. data/lib/natty-ui/spinner.rb +0 -120
  58. data/lib/natty-ui/text/east_asian_width.rb +0 -2529
  59. data/lib/natty-ui/text.rb +0 -203
  60. data/lib/natty-ui/wrapper/animate.rb +0 -17
  61. data/lib/natty-ui/wrapper/ask.rb +0 -78
  62. data/lib/natty-ui/wrapper/element.rb +0 -79
  63. data/lib/natty-ui/wrapper/features.rb +0 -21
  64. data/lib/natty-ui/wrapper/framed.rb +0 -45
  65. data/lib/natty-ui/wrapper/heading.rb +0 -64
  66. data/lib/natty-ui/wrapper/horizontal_rule.rb +0 -37
  67. data/lib/natty-ui/wrapper/list_in_columns.rb +0 -138
  68. data/lib/natty-ui/wrapper/message.rb +0 -109
  69. data/lib/natty-ui/wrapper/mixins.rb +0 -75
  70. data/lib/natty-ui/wrapper/progress.rb +0 -63
  71. data/lib/natty-ui/wrapper/query.rb +0 -89
  72. data/lib/natty-ui/wrapper/quote.rb +0 -25
  73. data/lib/natty-ui/wrapper/request.rb +0 -54
  74. data/lib/natty-ui/wrapper/section.rb +0 -118
  75. data/lib/natty-ui/wrapper/table.rb +0 -550
  76. data/lib/natty-ui/wrapper/task.rb +0 -55
  77. data/lib/natty-ui/wrapper.rb +0 -239
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'terminal/text'
4
+ require_relative 'width_finder'
5
+
6
+ module NattyUI
7
+ class TableRenderer
8
+ def self.[](table, max_width)
9
+ columns = table.columns.map(&:width)
10
+ return [] if columns.empty?
11
+ attributes = table.attributes
12
+ unless attributes.border_chars.nil?
13
+ max_width -= (columns.size - 1)
14
+ max_width -= 2 if attributes.border_around
15
+ end
16
+ return [] if max_width < columns.size
17
+ new(columns, table.each.to_a, attributes, max_width).lines
18
+ end
19
+
20
+ attr_reader :lines
21
+
22
+ private
23
+
24
+ def initialize(columns, rows, attributes, max_width)
25
+ @max_width, @columns = WidthFinder.find(columns, max_width)
26
+ init_borders(attributes)
27
+ @columns = @columns.each.with_index
28
+ @lines = render(rows.shift)
29
+ @lines.unshift(@b_top) if @b_top
30
+ rows.each do |row|
31
+ @lines << @b_between if @b_between
32
+ @lines += render(row)
33
+ end
34
+ @lines << @b_bottom if @b_bottom
35
+ end
36
+
37
+ def render(row)
38
+ cells = @columns.map { |cw, i| Cell.new(row[i], cw) }
39
+ height = cells.max_by { _1.text.size }.text.size
40
+ cells.each { _1.correct_height(height) }
41
+ Array.new(height) do |line|
42
+ "#{@b_outer}#{cells.map { _1.text[line] }.join(@b_inner)}#{@b_outer}"
43
+ end
44
+ end
45
+
46
+ def init_borders(attributes)
47
+ chars = attributes.border_chars or return
48
+ style = border_style(attributes)
49
+ @b_inner = style[chars[9]]
50
+ return init_borders_around(chars, style) if attributes.border_around
51
+ @b_between = chars[10] * (@max_width + @columns.size - 1)
52
+ i = -1
53
+ @columns[0..-2].each { |w| @b_between[i += w + 1] = chars[4] }
54
+ @b_between = style[@b_between]
55
+ end
56
+
57
+ def init_borders_around(chars, style)
58
+ fw = chars[10] * (@max_width + @columns.size - 1)
59
+ @b_top = "#{chars[0]}#{fw}#{chars[2]}"
60
+ @b_bottom = "#{chars[6]}#{fw}#{chars[8]}"
61
+ @b_between = "#{chars[3]}#{fw}#{chars[5]}"
62
+ i = 0
63
+ @columns[0..-2].each do |w|
64
+ @b_top[i += w + 1] = chars[1]
65
+ @b_bottom[i] = chars[7]
66
+ @b_between[i] = chars[4]
67
+ end
68
+ @b_top = style[@b_top]
69
+ @b_bottom = style[@b_bottom]
70
+ @b_between = style[@b_between]
71
+ @b_outer = @b_inner
72
+ end
73
+
74
+ def border_style(attributes)
75
+ style = attributes.border_style_bbcode
76
+ style ? ->(line) { "#{style}#{line}[/]" } : lambda(&:itself)
77
+ end
78
+
79
+ class Cell
80
+ attr_reader :width, :text
81
+
82
+ def correct_height(height)
83
+ return self if (diff = height - @text.size) <= 0
84
+ @text =
85
+ case @vertical
86
+ when :bottom
87
+ [Array.new(diff, @empty), @text]
88
+ when :middle
89
+ tc = diff / 2
90
+ [Array.new(tc, @empty), @text, Array.new(diff - tc, @empty)]
91
+ else
92
+ [@text, Array.new(diff, @empty)]
93
+ end.flatten(1)
94
+ self
95
+ end
96
+
97
+ def initialize(cell, width)
98
+ return @text = [] unless cell
99
+ att = cell.attributes
100
+ @padding = att.padding.dup
101
+ @align = att.align
102
+ @vertical = att.vertical
103
+ @style = att.style_bbcode
104
+ @text = width_corrected(cell.text, width)
105
+ end
106
+
107
+ private
108
+
109
+ def width_corrected(text, width)
110
+ @width, @padding[3], @padding[1] =
111
+ WidthFinder.adjust(width, @padding[3], @padding[1])
112
+ @empty = @style ? "#{@style}#{' ' * width}[/]" : ' ' * width
113
+ [
114
+ Array.new(@padding[0], @empty),
115
+ Text.each_line_with_size(
116
+ *text,
117
+ limit: @width,
118
+ bbcode: true,
119
+ ansi: Terminal.ansi?
120
+ ).map(&txt_fmt),
121
+ Array.new(@padding[2], @empty)
122
+ ].flatten(1)
123
+ end
124
+
125
+ def txt_fmt
126
+ lpad, rpad = pads
127
+ case @align
128
+ when :right
129
+ ->(txt, w) { "#{lpad}#{' ' * (@width - w)}#{txt}#{rpad}" }
130
+ when :centered
131
+ lambda do |txt, w|
132
+ s = @width - w
133
+ "#{lpad}#{' ' * (lw = s / 2)}#{txt}#{' ' * (s - lw)}#{rpad}"
134
+ end
135
+ else
136
+ ->(txt, w) { "#{lpad}#{txt}#{' ' * (@width - w)}#{rpad}" }
137
+ end
138
+ end
139
+
140
+ def pads
141
+ return ' ' * @padding[3], "[/]#{' ' * @padding[1]}" unless @style
142
+ ["#{@style}#{' ' * @padding[3]}", "#{' ' * @padding[1]}[/]"]
143
+ end
144
+ end
145
+ end
146
+ private_constant :TableRenderer
147
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'element'
4
+
5
+ module NattyUI
6
+ # {Element} implemting a task display section used by {Features.task}.
7
+ #
8
+ class Task < Temporary
9
+ include StateMixin
10
+
11
+ private
12
+
13
+ def finish_ok(text)
14
+ NattyUI.back_to_line(@start_line)
15
+ @start_line = nil
16
+ cm = Theme.current.mark(:checkmark)
17
+ text << @title if text.empty?
18
+ @parent.puts(
19
+ *text,
20
+ first_line_prefix: cm,
21
+ first_line_prefix_width: cm.width,
22
+ pin: @pin
23
+ )
24
+ super()
25
+ end
26
+
27
+ def initialize(parent, title, msg, pin)
28
+ super(parent)
29
+ @title = title
30
+ @prefix = '  '
31
+ @prefix_width = 2
32
+ @pin = pin
33
+ style = Theme.current.task_style
34
+ parent.puts(
35
+ title,
36
+ first_line_prefix: "#{style}➔ ",
37
+ first_line_prefix_width: 2,
38
+ prefix: "#{style}  ",
39
+ prefix_width: 2
40
+ )
41
+ puts(*msg) unless msg.empty?
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'element'
4
+
5
+ module NattyUI
6
+ # {Element} implemting a temprary display section used by
7
+ # {Features.temporary}.
8
+ #
9
+ class Temporary < Element
10
+ # @!visibility private
11
+ def puts(*objects, **options)
12
+ return self if @state
13
+ if options.delete(:pin)
14
+ @pins ||= []
15
+ @pins << [objects, options.except(:prefix_width, :suffix_width)]
16
+ end
17
+ super
18
+ end
19
+
20
+ # @!visibility private
21
+ def done
22
+ return self if @state
23
+ NattyUI.back_to_line(@start_line) if @start_line
24
+ @pins&.each { |objects, options| puts(*objects, **options) }
25
+ @state = :ok
26
+ self
27
+ end
28
+
29
+ private
30
+
31
+ alias finish_ok done
32
+
33
+ def initialize(parent)
34
+ @start_line = NattyUI.lines_written
35
+ super
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,303 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'utils'
4
+
5
+ module NattyUI
6
+ # @todo This chapter needs more documentation.
7
+ #
8
+ # A theme defines the style of elements.
9
+ #
10
+ class Theme
11
+ class << self
12
+ # Currently used theme
13
+ #
14
+ # @return [Compiled]
15
+ attr_reader :current
16
+
17
+ # @attribute [w] current
18
+ def current=(value)
19
+ case value
20
+ when Theme::Compiled
21
+ @current = value
22
+ when Theme
23
+ @current = value.compiled
24
+ else
25
+ raise(TypeError, 'Theme expected')
26
+ end
27
+ end
28
+
29
+ # Create a theme.
30
+ #
31
+ # @return [Theme] new theme
32
+ def create
33
+ theme = new
34
+ yield(theme) if block_given?
35
+ theme
36
+ end
37
+
38
+ # Default theme.
39
+ #
40
+ # @attribute [r] default
41
+ # @return [Theme] default theme
42
+ def default
43
+ create do |theme|
44
+ theme.heading_sytle = :bright_blue
45
+ theme.task_style = %i[bright_green b]
46
+ # theme.choice_style =
47
+ theme.choice_current_style = %i[bright_white on_blue b]
48
+ theme.define_marker(
49
+ bullet: '[bright_white]•[/fg]',
50
+ checkmark: '[bright_green]✓[/fg]',
51
+ quote: '[bright_blue]▍[/fg]',
52
+ information: '[bright_yellow]𝒊[/fg]',
53
+ warning: '[bright_yellow]![/fg]',
54
+ error: '[red]𝙓[/fg]',
55
+ failed: '[bright_red]𝑭[/fg]',
56
+ choice: '[bright_white]◦[/fg]',
57
+ current_choice: '[bright_green]◉[/fg]'
58
+ )
59
+ theme.define_section(
60
+ default: :bright_blue,
61
+ message: :bright_blue,
62
+ information: :bright_blue,
63
+ warning: :bright_yellow,
64
+ error: :red,
65
+ failed: :bright_red
66
+ )
67
+ end
68
+ end
69
+ end
70
+
71
+ attr_accessor :section_border
72
+ attr_reader :mark,
73
+ :border,
74
+ :heading,
75
+ :heading_sytle,
76
+ :section_styles,
77
+ :task_style,
78
+ :choice_current_style,
79
+ :choice_style
80
+
81
+ def heading_sytle=(value)
82
+ @heading_sytle = Utils.style(value)
83
+ end
84
+
85
+ def task_style=(value)
86
+ @task_style = Utils.style(value)
87
+ end
88
+
89
+ def choice_current_style=(value)
90
+ @choice_current_style = Utils.style(value)
91
+ end
92
+
93
+ def choice_style=(value)
94
+ @choice_style = Utils.style(value)
95
+ end
96
+
97
+ def compiled = Compiled.new(self).freeze
98
+
99
+ def define_marker(**defs)
100
+ @mark.merge!(defs)
101
+ self
102
+ end
103
+
104
+ def define_border(**defs)
105
+ defs.each_pair do |name, str|
106
+ s = str.to_s
107
+ case Text.width(s, bbcode: false)
108
+ when 1
109
+ @border[name.to_sym] = "#{s * 11}  "
110
+ when 11
111
+ @border[name.to_sym] = "#{s}  "
112
+ when 13
113
+ @border[name.to_sym] = s
114
+ else
115
+ raise(
116
+ TypeError,
117
+ "invalid boder definition for #{name} - #{str.inspect}"
118
+ )
119
+ end
120
+ end
121
+ self
122
+ end
123
+
124
+ def define_heading(*defs)
125
+ @heading = defs.flatten.take(6)
126
+ @heading += Array.new(6 - @heading.size, @heading[-1])
127
+ self
128
+ end
129
+
130
+ def define_section(**defs)
131
+ defs.each_pair do |name, style|
132
+ style = Utils.style(style)
133
+ @section_styles[name.to_sym] = style if style
134
+ end
135
+ self
136
+ end
137
+
138
+ class Compiled
139
+ attr_reader :task_style, :choice_current_style, :choice_style
140
+
141
+ def defined_marks = @mark.keys.sort!
142
+ def defined_borders = @border.keys.sort!
143
+
144
+ def heading(index) = @heading[index.to_i.clamp(1, 6) - 1]
145
+
146
+ def mark(value)
147
+ return @mark[value] if value.is_a?(Symbol)
148
+ (element = Str.new(value, true)).empty? ? @mark[:default] : element
149
+ end
150
+
151
+ def border(value)
152
+ return @border[value] if value.is_a?(Symbol)
153
+ case Text.width(value = value.to_s, bbcode: false)
154
+ when 1
155
+ "#{value * 11}  "
156
+ when 11
157
+ "#{value}  "
158
+ when 13
159
+ value
160
+ else
161
+ @border[:default]
162
+ end
163
+ end
164
+
165
+ def section_border(kind)
166
+ kind.is_a?(Symbol) ? @sections[kind] : @sections[:default]
167
+ end
168
+
169
+ def initialize(theme)
170
+ @heading = create_heading(theme.heading, theme.heading_sytle).freeze
171
+ @border = create_border(theme.border).freeze
172
+ @mark = create_mark(theme.mark).freeze
173
+ @task_style = as_style(theme.task_style)
174
+ @choice_current_style = as_style(theme.choice_current_style)
175
+ @choice_style = as_style(theme.choice_style)
176
+ @sections =
177
+ create_sections(
178
+ SectionBorder.create(border(theme.section_border)),
179
+ theme.section_styles.dup.compare_by_identity
180
+ )
181
+ end
182
+
183
+ private
184
+
185
+ def as_style(value) = (Ansi[*value].freeze if value)
186
+
187
+ def create_sections(template, styles)
188
+ Hash
189
+ .new do |h, kind|
190
+ h[kind] = SectionBorder.new(*template.parts(styles[kind])).freeze
191
+ end
192
+ .compare_by_identity
193
+ end
194
+
195
+ def create_mark(mark)
196
+ return {} if mark.empty?
197
+ with_default(mark.to_h { |n, e| [n.to_sym, Str.new("#{e} ")] })
198
+ end
199
+
200
+ def create_border(border)
201
+ return {} if border.empty?
202
+ with_default(border.transform_values { _1.dup.freeze })
203
+ end
204
+
205
+ def create_heading(heading, style)
206
+ return create_styled_heading(heading, style) if style
207
+ heading.map do |left|
208
+ right = " #{left.reverse}"
209
+ [left = Str.new("#{left} ", true), Str.new(right, left.size)]
210
+ end
211
+ end
212
+
213
+ def create_styled_heading(heading, style)
214
+ heading.map do |left|
215
+ right = Ansi.decorate(left.reverse, *style)
216
+ [
217
+ left = Str.new("#{Ansi.decorate(left, *style)} ", true),
218
+ Str.new(" #{right}", left.size)
219
+ ]
220
+ end
221
+ end
222
+
223
+ def with_default(map)
224
+ map.default = (map[:default] ||= map[map.first.first])
225
+ map.compare_by_identity
226
+ end
227
+
228
+ SectionBorder =
229
+ Struct.new(:top, :top_left, :top_right, :bottom, :prefix) do
230
+ def self.create(border)
231
+ mid = border[10] * 2
232
+ mid2 = mid * 2
233
+ right = "#{border[11]}#{border[12] * 2}"
234
+ new(
235
+ border[0] + mid2 + right,
236
+ border[0] + mid,
237
+ mid + right,
238
+ border[6] + mid2 + right,
239
+ border[9]
240
+ )
241
+ end
242
+
243
+ def parts(style)
244
+ unless style
245
+ return [
246
+ Str.new(top, 6),
247
+ Str.new("#{top_left} ", 4),
248
+ Str.new(" #{top_right}", 6),
249
+ Str.new(bottom, 6),
250
+ Str.new("#{prefix} ", 2)
251
+ ]
252
+ end
253
+ style = Ansi[*style]
254
+ [
255
+ # TODO: use rather [/fg]
256
+ Str.new("#{style}#{top}#{Ansi::RESET}", 6),
257
+ Str.new("#{style}#{top_left}#{Ansi::RESET} ", 4),
258
+ Str.new(" #{style}#{top_right}#{Ansi::RESET}", 6),
259
+ Str.new("#{style}#{bottom}#{Ansi::RESET}", 6),
260
+ Str.new("#{style}#{prefix}#{Ansi::RESET} ", 2)
261
+ ]
262
+ end
263
+ end
264
+
265
+ private_constant :SectionBorder
266
+ end
267
+
268
+ private
269
+
270
+ def initialize
271
+ define_heading(%w[╸╸╺╸╺━━━ ╴╶╴╶─═══ ╴╶╴╶─── ════ ━━━━ ────])
272
+ @mark = {
273
+ default: '•',
274
+ bullet: '•',
275
+ checkmark: '✓',
276
+ quote: '▍',
277
+ information: '𝒊',
278
+ warning: '!',
279
+ error: '𝙓',
280
+ failed: '𝑭',
281
+ choice: '◦',
282
+ current_choice: '◉'
283
+ }
284
+ @border = {
285
+ ######### 0123456789012
286
+ default: '┌┬┐├┼┤└┴┘│─╶╴',
287
+ defaulth: '───────── ─╶╴',
288
+ defaultv: '││││││││││ ',
289
+ double: '╔╦╗╠╬╣╚╩╝║═',
290
+ doubleh: '═════════ ═',
291
+ doublev: '║║║║║║║║║║ ',
292
+ heavy: '┏┳┓┣╋┫┗┻┛┃━╺╸',
293
+ heavyh: '━━━━━━━━━ ━╺╸',
294
+ heavyv: '┃┃┃┃┃┃┃┃┃┃ ',
295
+ rounded: '╭┬╮├┼┤╰┴╯│─╶╴'
296
+ }
297
+ @section_border = :rounded
298
+ @section_styles = {}
299
+ end
300
+
301
+ self.current = Terminal.colors == 2 ? new : default
302
+ end
303
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NattyUI
4
+ module Utils
5
+ class << self
6
+ # @!visibility private
7
+ def style(value)
8
+ value =
9
+ case value
10
+ when Array
11
+ value.dup
12
+ when Enumerable
13
+ value.to_a
14
+ when Symbol, Integer
15
+ [value]
16
+ when nil
17
+ return
18
+ else
19
+ value.to_s.delete_prefix('[').delete_suffix(']').split
20
+ end
21
+ value.uniq!
22
+ value.keep_if { Ansi.valid?(_1) }.empty? ? nil : value
23
+ end
24
+
25
+ # @!visibility private
26
+ def align(value)
27
+ value == :right || value == :centered ? value : :left
28
+ end
29
+
30
+ # @!visibility private
31
+ def vertical(value)
32
+ value == :bottom || value == :middle ? value : :top
33
+ end
34
+
35
+ # @!visibility private
36
+ def padding(*value)
37
+ value = value.flatten.take(4).map! { [0, _1.to_i].max }
38
+ case value.size
39
+ when 0
40
+ [0, 0, 0, 0]
41
+ when 1
42
+ [value[0], 0, 0, 0]
43
+ when 2
44
+ value * 2
45
+ when 3
46
+ value << value[1]
47
+ else
48
+ value
49
+ end
50
+ end
51
+ alias margin padding
52
+ end
53
+ end
54
+
55
+ class Str
56
+ attr_reader :to_s
57
+
58
+ alias to_str to_s
59
+ def inspect = to_s.inspect
60
+ def empty? = size == 0
61
+
62
+ def size
63
+ return @size if @size
64
+ @size = Text.width(self)
65
+ freeze
66
+ @size
67
+ end
68
+ alias width size
69
+
70
+ def initialize(str, size = nil)
71
+ @to_s = Ansi.bbcode(str).freeze
72
+ return unless size
73
+ @size = @size.is_a?(Integer) ? size : Text.width(self)
74
+ freeze
75
+ end
76
+ end
77
+
78
+ private_constant :Utils, :Str
79
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module NattyUI
4
4
  # The version number of the gem.
5
- VERSION = '0.12.1'
5
+ VERSION = '0.25.0'
6
6
  end