natty-ui 0.12.0 → 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 +23 -24
  4. data/examples/24bit-colors.rb +4 -9
  5. data/examples/3bit-colors.rb +28 -8
  6. data/examples/8bit-colors.rb +18 -23
  7. data/examples/attributes.rb +30 -25
  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 -18
  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 +47 -56
  36. data/examples/animate.rb +0 -44
  37. data/examples/attributes_list.rb +0 -14
  38. data/examples/demo.rb +0 -53
  39. data/examples/message.rb +0 -32
  40. data/examples/progress.rb +0 -68
  41. data/examples/query.rb +0 -41
  42. data/examples/read_key.rb +0 -13
  43. data/examples/table.rb +0 -41
  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 -521
  52. data/lib/natty-ui/ansi_wrapper.rb +0 -199
  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 -60
  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 -67
  70. data/lib/natty-ui/wrapper/progress.rb +0 -74
  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 -551
  76. data/lib/natty-ui/wrapper/task.rb +0 -55
  77. data/lib/natty-ui/wrapper.rb +0 -230
@@ -1,551 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'element'
4
-
5
- module NattyUI
6
- module Features
7
- #
8
- # Table view of data.
9
- #
10
- # @overload table(*args, type: :default, expand: false)
11
- # Display the given arrays as rows of a table.
12
- #
13
- # @param [#map<#map<#to_s>>] args one or more arrays representing rows of the table
14
- # @param [Symbol, String] type frame type; see {NattyUI::Frame}
15
- # @param [false, true. :equal] expand
16
- #
17
- # @example
18
- # ui.table(
19
- # %w[name price origin],
20
- # %w[apple 1$ California],
21
- # %w[banana 2$ Brasil],
22
- # %w[kiwi 1.5$ Newzeeland]
23
- # )
24
- #
25
- # # name │ price │ origin
26
- # # ───────┼───────┼───────────
27
- # # apple │ 1$ │ California
28
- # # ───────┼───────┼───────────
29
- # # banana │ 2$ │ Brasil
30
- # # ───────┼───────┼───────────
31
- # # kiwi │ 1.5$ │ Newzeeland
32
- #
33
- # @overload table(type: :default, expand: false)
34
- # Construct and display a table.
35
- #
36
- # @param [Symbol, String] type frame type; see {NattyUI::Frame}
37
- # @param [false, true. :equal] expand
38
- #
39
- # @example
40
- # ui.table(type: :heavy, expand: true) do |table|
41
- # table.add('name', 'price', 'origin', style: 'bold green')
42
- # table.add('apple', '1$', 'California')
43
- # table.add('banana', '2$', 'Brasil')
44
- # table.add('kiwi', '1.5$', 'Newzeeland')
45
- # table.align_column(0, :right).align_row(0, :center)
46
- # end
47
- #
48
- # # name ┃ price ┃ origin
49
- # # ━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━
50
- # # apple ┃ 1$ ┃ California
51
- # # ━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━
52
- # # banana ┃ 2$ ┃ Brasil
53
- # # ━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━
54
- # # kiwi ┃ 1.5$ ┃ Newzeeland
55
- #
56
- # @yield [Table] table construction helper
57
- # @return [Wrapper::Section, Wrapper] it's parent object
58
- def table(*table, type: :default, expand: false)
59
- type = NattyUI::Frame[type]
60
- table = Table.create(*table)
61
- yield(table) if block_given?
62
- _element(:Table, table, type, expand)
63
- end
64
-
65
- #
66
- # Table-like display of key/value pairs.
67
- #
68
- # @param [#to_s] seperator
69
- # @param [Hash<#to_s,#to_s>] kwargs
70
- # @return [Wrapper::Section, Wrapper] it's parent object
71
- #
72
- # @example
73
- # ui.pairs(apple: '1$', banana: '2$', kiwi: '1.5$')
74
- #
75
- # # output:
76
- # # apple: 1$
77
- # # banana: 2$
78
- # # kiwi: 1.5$
79
- #
80
- def pairs(seperator = ': ', **kwargs)
81
- kwargs.empty? ? self : _element(:Pairs, kwargs, seperator)
82
- end
83
-
84
- #
85
- # Helper class to define a table layout.
86
- # @see #table
87
- #
88
- class Table
89
- # @!visibility private
90
- def self.create(*table)
91
- table = table[0] if table.size == 1 && table[0].respond_to?(:each)
92
- return table if table.is_a?(self.class)
93
- ret = new
94
- table.each { ret.add_row(*_1) }
95
- ret
96
- end
97
-
98
- # @!visibility private
99
- def initialize = @rows = []
100
-
101
- # @return [Integer] count of rows
102
- def row_count = @rows.size
103
-
104
- # @return [Integer] count of columns
105
- def col_count = @rows.max_by { _1&.size || 0 }&.size || 0
106
-
107
- # Get the {Cell} at a table position.
108
- #
109
- # @param [Integer] row row index
110
- # @param [Integer] col column index
111
- # @return [Cell] at row/column
112
- # @return [nil] if no cell defined at row/column
113
- def [](row, col) = @rows.dig(row, col)
114
-
115
- # Change {Cell} or (text) value at a table position.
116
- #
117
- # @example change {Cell} at row 2, column 3
118
- # table[2, 3] = table.cell('Hello World', align: right, style: 'bold')
119
- # @example change text at row 2, column 3
120
- # table[2, 3] = 'Hello Ruby!'
121
- # @example delete {Cell} at row 2, column 3
122
- # table[2, 3] = nil
123
- #
124
- # @param [Integer] row row index
125
- # @param [Integer] col column index
126
- # @param [Cell, #to_s, nil] value Cell or text to use at specified position
127
- # @return [Cell, #to_s, nil] the value
128
- def []=(row, col, value)
129
- row = (@rows[row] ||= [])
130
- if value.nil? || value.is_a?(Cell)
131
- row[col] = value
132
- else
133
- cell = row[col]
134
- cell ? cell.value = value : row[col] = Cell.new(value, nil, nil)
135
- end
136
- end
137
-
138
- # Create a new cell.
139
- #
140
- # @example create a {Cell} with right aligned bold text "Hello World"
141
- # table[2, 3] = table.cell('Hello World', align: right, style: 'bold')
142
- #
143
- # @param [#to_s] value text value
144
- # @param [:left, :right, :center] align text alignment
145
- # @param [String] style text style; see {Ansi.try_convert}
146
- # @return [Cell] a new cell
147
- def cell(value, align: :left, style: nil) = Cell.new(value, align, style)
148
-
149
- # Add a new row to the table.
150
- #
151
- # @example add a row with three right-aligned columns
152
- # table.add_row('One', 'Two', 'Three', align: :right)
153
- #
154
- # @param [#map] columns Enumerable-like object containing column texts
155
- # @param [:left, :right, :center] align text alignment
156
- # @param [String] style text style; see {Ansi.try_convert}
157
- # @return [Table] itself
158
- def add_row(*columns, align: nil, style: nil)
159
- if columns.size == 1 && columns[0].respond_to?(:map)
160
- columns = columns[0]
161
- end
162
- columns =
163
- columns.map { |cell| as_cell(cell, align, style) if cell }.to_a
164
- @rows << (columns.empty? ? nil : columns)
165
- self
166
- end
167
- alias add add_row
168
-
169
- # Add a new column to the table.
170
- #
171
- # @example add a column of three rows with bold styled text
172
- # table.add_column('One', 'Two', 'Three', style: :bold)
173
- #
174
- # @param [#map] rows Enumerable-like object containing texts for each row
175
- # @param [:left, :right, :center] align text alignment
176
- # @param [String] style text style; see {Ansi.try_convert}
177
- # @return [Table] itself
178
- def add_column(*rows, align: nil, style: nil)
179
- rows = rows[0] if rows.size == 1 && rows[0].respond_to?(:map)
180
- row_idx = -1
181
- rows.each do |cell|
182
- (@rows[row_idx += 1] ||= []) << as_cell(cell, align, style)
183
- end
184
- end
185
-
186
- # Change style of one or more rows.
187
- #
188
- # @example define bold red text style for the first row
189
- # table.style_row(0, 'bold red')
190
- # @example define yellow text style for the first three rows
191
- # table.style_row(0..2, 'yellow')
192
- # @example define green text style for rows 3, 4 and 7
193
- # table.style_row([3, 4, 7], 'green')
194
- #
195
- # @param [Integer, Enumerable<Integer>] row index of row(s) to change
196
- # @param [String, nil] style text style; see {Ansi.try_convert}
197
- # @return [Table] itself
198
- def style_row(row, style)
199
- if row.is_a?(Integer)
200
- row = [row]
201
- elsif !row.is_a?(Enumerable)
202
- raise(TypeError, "invalid row value - #{row}")
203
- end
204
- row.each { |r| @rows[r]&.each { _1&.style = style } }
205
- self
206
- end
207
-
208
- # Change style of one or more columns.
209
- #
210
- # @example define bold red text style for the first column
211
- # table.style_column(0, 'bold red')
212
- # @example define yellow text style for the first three columns
213
- # table.style_column(0..2, 'yellow')
214
- # @example define green text style for columns with index 3, 4 and 7
215
- # table.style_column([3, 4, 7], 'green')
216
- #
217
- # @param [Integer, Enumerable<Integer>] column index of column(s) to change
218
- # @param [String, nil] style text style; see {Ansi.try_convert}
219
- # @return [Table] itself
220
- def style_column(column, style)
221
- if column.is_a?(Integer)
222
- column = [column]
223
- elsif !column.is_a?(Enumerable)
224
- raise(TypeError, "invalid column value - #{column}")
225
- end
226
- @rows.each { |row| column.each { row[_1]&.style = style } }
227
- self
228
- end
229
-
230
- # Change text alignment of one or more rows.
231
- #
232
- # @example align first row right
233
- # table.align_row(0, :right)
234
- # @example center first three rows
235
- # table.align_row(0..2, :center)
236
- # @example center the rows with index 3, 4 and 7
237
- # table.align_row([3, 4, 7], :center)
238
- #
239
- # @param [Integer, Enumerable<Integer>] row index of row(s) to change
240
- # @param [:left, :right, :center] alignment
241
- # @return [Table] itself
242
- def align_row(row, alignment)
243
- if row.is_a?(Integer)
244
- row = [row]
245
- elsif !row.is_a?(Enumerable)
246
- raise(TypeError, "invalid row value - #{row}")
247
- end
248
- row.each { |r| @rows[r]&.each { _1&.align = alignment } }
249
- self
250
- end
251
-
252
- # Change text alignment of one or more column.
253
- #
254
- # @example align first column right
255
- # table.align_column(0, :right)
256
- # @example center first three columns
257
- # table.align_column(0..2, :center)
258
- # @example center the columns with index 3, 4 and 7
259
- # table.align_column([3, 4, 7], :center)
260
- #
261
- # @param [Integer, Enumerable<Integer>] column index of column(s) to change
262
- # @param [:left, :right, :center] alignment
263
- # @return [Table] itself
264
- def align_column(column, alignment)
265
- if column.is_a?(Integer)
266
- column = [column]
267
- elsif !column.is_a?(Enumerable)
268
- raise(TypeError, "invalid column value - #{column}")
269
- end
270
- @rows.each { |row| column.each { row[_1]&.align = alignment } }
271
- self
272
- end
273
-
274
- # Convert the table to the compactest (two-dimensional) array
275
- # representation.
276
- #
277
- # @return [Array<Array<Cell>>]
278
- def to_a
279
- ret = []
280
- ridx = -1
281
- @rows.each do |row|
282
- ridx += 1
283
- next unless row
284
- count = 0
285
- row =
286
- row.map do |cell|
287
- next unless cell
288
- next if cell.value.empty?
289
- count += 1
290
- cell.dup
291
- end
292
- ret[ridx] = row if count.positive?
293
- end
294
- ret
295
- end
296
-
297
- private
298
-
299
- def as_cell(value, align, style)
300
- return Cell.new(value, align, style) unless value.is_a?(Cell)
301
- cell = value.dup
302
- cell.align = align if align
303
- cell.style = style if style
304
- cell
305
- end
306
-
307
- def initialize_copy(*)
308
- super
309
- @rows = to_a
310
- end
311
-
312
- class Cell
313
- # @return [String, nil] text value of the cell
314
- attr_reader :value
315
-
316
- attr_writer :align, :style
317
-
318
- # @!visibility private
319
- attr_accessor :tag
320
-
321
- # @!visibility private
322
- def initialize(value, align, style)
323
- @value = value.to_s
324
- @align = align
325
- @style = style
326
- end
327
-
328
- # @attribute [r] align
329
- # @return [:left, :right, :center] text alignment
330
- def align = ALIGNMENT[@align]
331
-
332
- # @attribute [r] style
333
- # @return [String, nil] text style; see {Ansi.try_convert}
334
- def style
335
- @style_ ||= Ansi.try_convert(@style)
336
- end
337
-
338
- def value=(value)
339
- @value = value.to_s
340
- end
341
-
342
- private
343
-
344
- def initialize_copy(*)
345
- super
346
- @value = @value.dup
347
- @tag = nil
348
- end
349
-
350
- alignment = { left: :left, right: :right, center: :center }
351
- alignment.default = :left
352
- ALIGNMENT = alignment.compare_by_identity.freeze
353
- end
354
- end
355
- end
356
-
357
- class Wrapper
358
- # An {Element} to print a table.
359
- #
360
- # @see Features#table
361
- class Table < Element
362
- protected
363
-
364
- def call(table, frame, enlarge)
365
- TableGen.each_line(
366
- table.to_a,
367
- @parent.available_width - 1,
368
- frame,
369
- enlarge
370
- ) { |line| @parent.puts(line) }
371
- @parent
372
- end
373
- end
374
-
375
- # An {Element} to print key/value pairs.
376
- #
377
- # @see Features#pairs
378
- class Pairs < Element
379
- protected
380
-
381
- def call(kwargs, seperator)
382
- TableGen.each_line_simple(
383
- kwargs.map do |k, v|
384
- [
385
- Features::Table::Cell.new(k, :right, nil),
386
- Features::Table::Cell.new(v, :left, nil)
387
- ]
388
- end,
389
- @parent.available_width - 1,
390
- seperator
391
- ) { @parent.puts(_1) }
392
- @parent
393
- end
394
- end
395
-
396
- class TableGen
397
- COLOR = Ansi[39]
398
-
399
- def self.each_line(table, max_width, frame, expand)
400
- gen = new(table, max_width, 3, expand)
401
- return unless gen.ok?
402
- col_div = "#{Ansi::RESET} #{COLOR}#{frame[4]}#{Ansi::RESET} "
403
- row_div = "#{frame[5]}#{frame[6]}#{frame[5]}"
404
- row_div =
405
- "#{COLOR}#{
406
- gen.widths.map { frame[5] * _1 }.join(row_div)
407
- }#{Ansi::RESET}"
408
- last_row = 0
409
- gen.each do |number, line|
410
- if last_row != number
411
- last_row = number
412
- yield(row_div)
413
- end
414
- yield(line.join(col_div) + Ansi::RESET)
415
- end
416
- end
417
-
418
- def self.each_line_simple(table, max_width, seperator)
419
- gen = new(table, max_width, Text.width(seperator), false)
420
- return unless gen.ok?
421
- col_div = "#{COLOR}#{seperator}#{Ansi::RESET}"
422
- gen.each { yield(_2.join(col_div)) }
423
- end
424
-
425
- attr_reader :widths
426
-
427
- def initialize(table, max_width, coldiv_size, expand)
428
- return if coldiv_size > max_width # wtf
429
- @table = table
430
- @col_count = table.max_by { _1&.size || 0 }&.size || 0
431
- @max_width = max_width
432
- @widths = determine_widths
433
- sum = @widths.sum
434
- space = @max_width - ((@col_count - 1) * coldiv_size)
435
- if space < sum
436
- @widths = reduce_widths(sum, space, coldiv_size)
437
- adjust!
438
- elsif expand && sum < space
439
- if expand == :equal
440
- equal_widths(space)
441
- else
442
- enlarge_widths(sum, space)
443
- end
444
- adjust!
445
- end
446
- @empties = @widths.map { ' ' * _1 }
447
- end
448
-
449
- def ok? = @empties != nil
450
-
451
- def each
452
- ridx = -1
453
- @table.each do |row|
454
- ridx += 1
455
- next yield(ridx, @empties) unless row
456
- line_count = row.map { _1 ? _1.tag.lines.size : 0 }.max
457
- line_count.times do |lidx|
458
- cidx = -1
459
- yield(
460
- ridx,
461
- @empties.map do |spacer|
462
- cell = row[cidx += 1] or next spacer
463
- str, str_size = cell.tag.lines[lidx]
464
- next spacer unless str_size
465
- str_fmt(cell.align, cell.style, str, str_size, spacer.size)
466
- end
467
- )
468
- end
469
- end
470
- nil
471
- end
472
-
473
- private
474
-
475
- def str_fmt(align, style, str, str_size, size)
476
- return "#{style}#{str}" unless (size -= str_size).positive?
477
- return "#{style}#{' ' * size}#{str}" if align == :right
478
- if align == :center
479
- right = size / 2
480
- return "#{style}#{' ' * (size - right)}#{str}#{' ' * right}"
481
- end
482
- "#{style}#{str}#{' ' * size}"
483
- end
484
-
485
- def adjust!
486
- @table.each do |row|
487
- next unless row
488
- cidx = -1
489
- row.each do |cell|
490
- cidx += 1
491
- next unless cell
492
- width = @widths[cidx]
493
- next if cell.tag.value <= width
494
- cell.tag.lines = Text.as_lines([cell.value], width)
495
- end
496
- end
497
- end
498
-
499
- def enlarge_widths(sum, space)
500
- @widths.map! { ((((100.0 * _1) / sum) * space) / 100).round }
501
- return if (diff = space - @widths.sum).zero?
502
- @widths[
503
- @widths.index(diff.negative? ? @widths.max : @widths.min)
504
- ] += diff
505
- end
506
-
507
- def equal_widths(space)
508
- @widths = Array.new(@widths.size, space / @widths.size)
509
- @widths[-1] += space - @widths.sum
510
- end
511
-
512
- def reduce_widths(sum, space, coldiv_size)
513
- ws = @widths.dup
514
- until sum <= space
515
- max = ws.max
516
- if max == 1
517
- ws = @widths.take(ws.size - 1)
518
- sum = ws.sum
519
- space += coldiv_size
520
- else
521
- while (idx = ws.rindex(max)) && (sum > space)
522
- ws[idx] -= 1
523
- sum -= 1
524
- end
525
- end
526
- end
527
- ws
528
- end
529
-
530
- def determine_widths
531
- ret = Array.new(@col_count, 1)
532
- @table.each do |row|
533
- next unless row
534
- cidx = -1
535
- row.each do |cell|
536
- cidx += 1
537
- next unless cell
538
- cell.tag = Tag.new(Text.as_lines([cell.value], @max_width))
539
- width = cell.tag.value = cell.tag.lines.max_by(&:last).last
540
- ret[cidx] = width if ret[cidx] < width
541
- end
542
- end
543
- ret
544
- end
545
-
546
- Tag = Struct.new(:lines, :value)
547
- end
548
-
549
- private_constant :TableGen
550
- end
551
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'section'
4
- require_relative 'mixins'
5
-
6
- module NattyUI
7
- module Features
8
- # Creates task section implementing additional {ProgressAttributes}.
9
- #
10
- # A task section has additional states and can be closed with {#completed}
11
- # or {#failed}.
12
- #
13
- # @param (see #information)
14
- # @yieldparam [Wrapper::Task] task the created section
15
- # @return [Object] the result of the code block
16
- # @return [Wrapper::Task] itself, when no code block is given
17
- def task(title, *args, &block)
18
- _section(:Task, args, title: title, &block)
19
- end
20
- end
21
-
22
- module TaskMethods
23
- protected
24
-
25
- def initialize(parent, title:)
26
- @temp = parent.wrapper.temporary
27
- @final_text = [title]
28
- super(parent, title: title, glyph: :task)
29
- end
30
-
31
- def finish
32
- return @parent.failed(*@final_text) if failed?
33
- @temp.call
34
- _section(
35
- :Message,
36
- @final_text,
37
- owner: @parent,
38
- title: @final_text.shift,
39
- glyph: @status = :completed
40
- )
41
- end
42
- end
43
- private_constant :TaskMethods
44
-
45
- class Wrapper
46
- #
47
- # A {Message} container to visualize the progression of a task.
48
- #
49
- # @see Features.task
50
- class Task < Message
51
- include ProgressAttributes
52
- include TaskMethods
53
- end
54
- end
55
- end