tty 0.0.10 → 0.0.11

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 (56) hide show
  1. data/README.md +75 -19
  2. data/lib/tty.rb +7 -0
  3. data/lib/tty/shell/question.rb +3 -3
  4. data/lib/tty/shell/response.rb +5 -5
  5. data/lib/tty/table.rb +51 -19
  6. data/lib/tty/table/column_set.rb +42 -1
  7. data/lib/tty/table/columns.rb +167 -0
  8. data/lib/tty/table/field.rb +2 -2
  9. data/lib/tty/table/indentation.rb +54 -0
  10. data/lib/tty/table/operation/escape.rb +1 -1
  11. data/lib/tty/table/operation/filter.rb +0 -1
  12. data/lib/tty/table/operation/padding.rb +95 -0
  13. data/lib/tty/table/operation/wrapped.rb +6 -3
  14. data/lib/tty/table/operations.rb +3 -2
  15. data/lib/tty/table/orientation/horizontal.rb +27 -1
  16. data/lib/tty/table/orientation/vertical.rb +17 -1
  17. data/lib/tty/table/padder.rb +142 -0
  18. data/lib/tty/table/renderer/basic.rb +101 -31
  19. data/lib/tty/table/validatable.rb +0 -7
  20. data/lib/tty/terminal/color.rb +36 -20
  21. data/lib/tty/text/truncation.rb +16 -1
  22. data/lib/tty/text/wrapping.rb +31 -10
  23. data/lib/tty/version.rb +1 -1
  24. data/spec/tty/shell/question/argument_spec.rb +1 -1
  25. data/spec/tty/shell/question/modify_spec.rb +2 -2
  26. data/spec/tty/shell/response/read_email_spec.rb +0 -1
  27. data/spec/tty/table/border/ascii/rendering_spec.rb +34 -7
  28. data/spec/tty/table/border/null/rendering_spec.rb +34 -7
  29. data/spec/tty/table/column_set/extract_widths_spec.rb +1 -1
  30. data/spec/tty/table/column_set/widths_from_spec.rb +52 -0
  31. data/spec/tty/table/columns/enforce_spec.rb +68 -0
  32. data/spec/tty/table/columns/widths_spec.rb +33 -0
  33. data/spec/tty/table/indentation/insert_indent_spec.rb +27 -0
  34. data/spec/tty/table/operation/wrapped/call_spec.rb +2 -1
  35. data/spec/tty/table/operation/wrapped/wrap_spec.rb +3 -2
  36. data/spec/tty/table/operations/new_spec.rb +3 -5
  37. data/spec/tty/table/orientation_spec.rb +68 -22
  38. data/spec/tty/table/padder/parse_spec.rb +45 -0
  39. data/spec/tty/table/padding_spec.rb +120 -0
  40. data/spec/tty/table/renderer/ascii/indentation_spec.rb +41 -0
  41. data/spec/tty/table/renderer/ascii/padding_spec.rb +61 -0
  42. data/spec/tty/table/renderer/ascii/resizing_spec.rb +114 -0
  43. data/spec/tty/table/renderer/basic/alignment_spec.rb +18 -19
  44. data/spec/tty/table/renderer/basic/coloring_spec.rb +45 -0
  45. data/spec/tty/table/renderer/basic/indentation_spec.rb +46 -0
  46. data/spec/tty/table/renderer/basic/options_spec.rb +2 -2
  47. data/spec/tty/table/renderer/basic/padding_spec.rb +52 -0
  48. data/spec/tty/table/renderer/basic/resizing_spec.rb +96 -0
  49. data/spec/tty/table/renderer/render_spec.rb +36 -0
  50. data/spec/tty/table/renderer/unicode/indentation_spec.rb +41 -0
  51. data/spec/tty/table/renderer/unicode/padding_spec.rb +61 -0
  52. data/spec/tty/table/renderer_spec.rb +19 -0
  53. data/spec/tty/table/rotate_spec.rb +20 -6
  54. data/spec/tty/text/truncation/truncate_spec.rb +18 -3
  55. data/spec/tty/text/wrapping/wrap_spec.rb +24 -7
  56. metadata +56 -18
@@ -0,0 +1,142 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Table
5
+
6
+ # A class responsible for processing table field padding
7
+ class Padder
8
+ include TTY::Equatable
9
+
10
+ attr_reader :padding
11
+
12
+ # Initialize a Padder
13
+ #
14
+ # @api public
15
+ def initialize(padding)
16
+ @padding = padding
17
+ end
18
+
19
+ # Parse padding options
20
+ #
21
+ # @param [Object] value
22
+ #
23
+ # @return [TTY::Padder]
24
+ #
25
+ # @api public
26
+ def self.parse(value = nil)
27
+ return value if value.kind_of?(self)
28
+
29
+ padding = if value.class <= Numeric
30
+ [value, value, value, value]
31
+ elsif value.nil?
32
+ []
33
+ elsif value.size == 2
34
+ [value[0], value[1], value[0], value[1]]
35
+ elsif value.size == 4
36
+ value
37
+ else
38
+ raise ArgumentError, 'Wrong :padding parameter, must be an array'
39
+ end
40
+ new(padding)
41
+ end
42
+
43
+ # Top padding
44
+ #
45
+ # @return [Integer]
46
+ #
47
+ # @api public
48
+ def top
49
+ @padding[0].to_i
50
+ end
51
+
52
+ # Set top padding
53
+ #
54
+ # @param [Integer] val
55
+ #
56
+ # @api public
57
+ def top=(value)
58
+ @padding[0] = value
59
+ end
60
+
61
+ # Right padding
62
+ #
63
+ # @return [Integer]
64
+ #
65
+ # @api public
66
+ def right
67
+ @padding[1].to_i
68
+ end
69
+
70
+ # Set right padding
71
+ #
72
+ # @param [Integer] val
73
+ #
74
+ # @api public
75
+ def right=(value)
76
+ @padding[1] = value
77
+ end
78
+
79
+ # Bottom padding
80
+ #
81
+ # @return [Integer]
82
+ #
83
+ # @api public
84
+ def bottom
85
+ @padding[2].to_i
86
+ end
87
+
88
+ # Set bottom padding
89
+ #
90
+ # @param [Integer] value
91
+ #
92
+ # @api public
93
+ def bottom=(value)
94
+ @padding[2] = value
95
+ end
96
+
97
+ # Left padding
98
+ #
99
+ # @return [Integer]
100
+ #
101
+ # @api public
102
+ def left
103
+ @padding[3].to_i
104
+ end
105
+
106
+ # Set left padding
107
+ #
108
+ # @param [Integer] value
109
+ #
110
+ # @api public
111
+ def left=(value)
112
+ @padding[3] = value
113
+ end
114
+
115
+ # Check if padding is set
116
+ #
117
+ # @api public
118
+ def empty?
119
+ padding.empty?
120
+ end
121
+
122
+ # Check if vertical padding is applied
123
+ #
124
+ # @return [Boolean]
125
+ #
126
+ # @api public
127
+ def vertical?
128
+ top.nonzero? or bottom.nonzero?
129
+ end
130
+
131
+ # Check if horizontal padding is applied
132
+ #
133
+ # @return [Boolean]
134
+ #
135
+ # @api public
136
+ def horizontal?
137
+ left.nonzero? or right.nonzero?
138
+ end
139
+
140
+ end # Padder
141
+ end # Table
142
+ end # TTY
@@ -16,7 +16,7 @@ module TTY
16
16
  #
17
17
  # @api public
18
18
  attr_reader :table
19
- private :table
19
+ # private :table
20
20
 
21
21
  # Table border to be rendered
22
22
  #
@@ -57,6 +57,36 @@ module TTY
57
57
  # @api public
58
58
  attr_accessor :multiline
59
59
 
60
+ # The table indentation value
61
+ #
62
+ # @return [Integer]
63
+ #
64
+ # @api public
65
+ attr_accessor :indent
66
+
67
+ # The table totabl width
68
+ #
69
+ # @return [Integer]
70
+ #
71
+ # @api public
72
+ attr_accessor :width
73
+
74
+ # The table resizing behaviour. If true the algorithm will automatically
75
+ # expand or shrink table to fit the terminal width or specified width.
76
+ # By default its false.
77
+ #
78
+ # @return [Integer]
79
+ #
80
+ # @api public
81
+ attr_accessor :resize
82
+
83
+ # The table padding settings
84
+ #
85
+ # @return [TTY::Table::Padder]
86
+ #
87
+ # @api public
88
+ attr_reader :padding
89
+
60
90
  # Initialize a Renderer
61
91
  #
62
92
  # @param [Hash] options
@@ -64,31 +94,39 @@ module TTY
64
94
  # used to format table individual column alignment
65
95
  # @option options [String] :column_widths
66
96
  # used to format table individula column width
67
- #
68
- # :indent - Indent the first column by indent value
69
- # :padding - Pad out the row cell by padding value
97
+ # @option options [Integer] :indent
98
+ # indent the first column by indent value
99
+ # @option options [Integer,Array] :padding
100
+ # add padding to table fields
70
101
  #
71
102
  # @return [TTY::Table::Renderer::Basic]
72
103
  #
73
104
  # @api private
74
- def initialize(table, options={})
75
- validate_rendering_options!(options)
105
+ def initialize(table, options = {})
76
106
  @table = table || (raise ArgumentRequired, "Expected TTY::Table instance, got #{table.inspect}")
77
107
  @multiline = options.fetch(:multiline) { false }
78
108
  @operations = TTY::Table::Operations.new(table)
79
- unless multiline
80
- @operations.add_operation(:escape, Operation::Escape.new)
81
- @operations.run_operations(:escape)
109
+ if not multiline
110
+ @operations.add(:escape, Operation::Escape.new)
82
111
  end
83
-
84
112
  @border = TTY::Table::BorderOptions.from(options.delete(:border))
85
- @column_widths = Array(options.fetch(:column_widths) {
86
- ColumnSet.new(table).extract_widths
87
- }).map(&:to_i)
113
+ @column_widths = options.fetch(:column_widths, nil)
88
114
  @column_aligns = Array(options.delete(:column_aligns)).map(&:to_sym)
89
115
  @filter = options.fetch(:filter) { proc { |val, row, col| val } }
90
116
  @width = options.fetch(:width) { TTY.terminal.width }
91
117
  @border_class = options.fetch(:border_class) { Border::Null }
118
+ @indent = options.fetch(:indent) { 0 }
119
+ @resize = options.fetch(:resize) { false }
120
+ @padding = TTY::Table::Padder.parse(options.fetch(:padding) { nil })
121
+ end
122
+
123
+ # Parses supplied column widths, if not present calculates natural widths
124
+ #
125
+ # @return [Array[Integer]]
126
+ #
127
+ # @api public
128
+ def column_widths
129
+ @column_widths = ColumnSet.widths_from(table, @column_widths)
92
130
  end
93
131
 
94
132
  # Store border characters, style and separator for the table rendering
@@ -113,10 +151,35 @@ module TTY
113
151
  #
114
152
  # @api private
115
153
  def add_operations
116
- operations.add_operation(:alignment, Operation::AlignmentSet.new(column_aligns, column_widths))
117
- operations.add_operation(:filter, Operation::Filter.new(filter))
118
- operations.add_operation(:truncation, Operation::Truncation.new(column_widths))
119
- operations.add_operation(:wrapping, Operation::Wrapped.new(column_widths))
154
+ operations.add(:alignment, Operation::AlignmentSet.new(column_aligns,
155
+ column_widths))
156
+ operations.add(:filter, Operation::Filter.new(filter))
157
+ operations.add(:truncation, Operation::Truncation.new(column_widths))
158
+ operations.add(:wrapping, Operation::Wrapped.new(column_widths, padding))
159
+ operations.add(:padding, Operation::Padding.new(padding, multiline))
160
+ end
161
+
162
+ # Initializes indentation
163
+ #
164
+ # @return [TTY::Table::Indentation]
165
+ #
166
+ # @api private
167
+ def indentation
168
+ @indentation ||= TTY::Table::Indentation.new(self)
169
+ end
170
+
171
+ # Delegate indentation insertion
172
+ #
173
+ # @api public
174
+ def insert_indent(line)
175
+ indentation.insert_indent(line)
176
+ end
177
+
178
+ # Return column contraints
179
+ #
180
+ # @api private
181
+ def columns_constraints
182
+ TTY::Table::Columns.new(self)
120
183
  end
121
184
 
122
185
  # Sets the output padding,
@@ -126,7 +189,7 @@ module TTY
126
189
  #
127
190
  # @api public
128
191
  def padding=(value)
129
- @padding = [0, value].max
192
+ @padding = TTY::Table::Padder.parse(value)
130
193
  end
131
194
 
132
195
  # Renders table
@@ -137,13 +200,15 @@ module TTY
137
200
  def render
138
201
  return if table.empty?
139
202
 
140
- # TODO: throw an error if too many columns as compared to terminal width
141
- # and then change table.orientation from vertical to horizontal
142
- # TODO: Decide about table orientation
203
+ operations.run_operations(:escape) unless multiline
204
+ columns_constraints.enforce
143
205
  add_operations
144
- ops = [:filter, :alignment]
206
+ ops = [:alignment]
207
+ ops << :padding unless padding.empty?
145
208
  multiline ? ops << :wrapping : ops << :truncation
209
+ ops << :filter
146
210
  operations.run_operations(*ops)
211
+
147
212
  render_data.compact.join("\n")
148
213
  end
149
214
 
@@ -153,12 +218,16 @@ module TTY
153
218
  #
154
219
  # @api private
155
220
  def render_data
156
- first_row = table.first
157
- data_border = border_class.new(column_widths, border)
158
- header = render_header(first_row, data_border)
221
+ first_row = table.first
222
+ data_border = border_class.new(column_widths, border)
223
+ header = render_header(first_row, data_border)
159
224
  rows_with_border = render_rows(data_border)
160
225
 
161
- [header, rows_with_border, data_border.bottom_line].compact
226
+ if bottom_line = data_border.bottom_line
227
+ insert_indent(bottom_line)
228
+ end
229
+
230
+ [header, rows_with_border, bottom_line].compact
162
231
  end
163
232
 
164
233
  # Format the header if present
@@ -175,7 +244,8 @@ module TTY
175
244
  def render_header(row, data_border)
176
245
  top_line = data_border.top_line
177
246
  if row.is_a?(TTY::Table::Header)
178
- [top_line, data_border.row_line(row), data_border.separator].compact
247
+ header = [top_line, data_border.row_line(row), data_border.separator]
248
+ insert_indent(header.compact)
179
249
  else
180
250
  top_line
181
251
  end
@@ -190,8 +260,8 @@ module TTY
190
260
  #
191
261
  # @api private
192
262
  def render_rows(data_border)
193
- rows = table.rows
194
- size = rows.size
263
+ rows = table.rows
264
+ size = rows.size
195
265
  rows.each_with_index.map do |row, index|
196
266
  render_row(row, data_border, size != (index += 1))
197
267
  end
@@ -213,9 +283,9 @@ module TTY
213
283
  row_line = data_border.row_line(row)
214
284
 
215
285
  if (border.separator == TTY::Table::Border::EACH_ROW) && is_last_row
216
- [row_line, separator]
286
+ insert_indent([row_line, separator])
217
287
  else
218
- row_line
288
+ insert_indent(row_line)
219
289
  end
220
290
  end
221
291
 
@@ -29,13 +29,6 @@ module TTY
29
29
  def assert_string_values(rows)
30
30
  end
31
31
 
32
- def validate_rendering_options!(options)
33
- if (column_widths = options[:column_widths]) &&
34
- (!column_widths.kind_of?(Array) || column_widths.empty?)
35
- raise InvalidArgument, ":column_widths must be a non-empty array"
36
- end
37
- end
38
-
39
32
  # Check if options are of required type
40
33
  #
41
34
  # @api private
@@ -16,28 +16,44 @@ module TTY
16
16
  STYLES = %w[ BOLD CLEAR UNDERLINE ].freeze
17
17
 
18
18
  # Escape codes for text color.
19
- BLACK = "\e[30m"
20
- RED = "\e[31m"
21
- GREEN = "\e[32m"
22
- YELLOW = "\e[33m"
23
- BLUE = "\e[34m"
24
- MAGENTA = "\e[35m"
25
- CYAN = "\e[36m"
26
- WHITE = "\e[37m"
27
-
28
- TEXT_COLORS = %w[ BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE ].freeze
19
+ BLACK = "\e[30m"
20
+ RED = "\e[31m"
21
+ GREEN = "\e[32m"
22
+ YELLOW = "\e[33m"
23
+ BLUE = "\e[34m"
24
+ MAGENTA = "\e[35m"
25
+ CYAN = "\e[36m"
26
+ WHITE = "\e[37m"
27
+
28
+ LIGHT_BLACK = "\e[90m"
29
+ LIGHT_RED = "\e[91m"
30
+ LIGHT_GREEN = "\e[92m"
31
+ LIGHT_YELLOW = "\e[93m"
32
+ LIGHT_BLUE = "\e[94m"
33
+ LIGHT_MAGENTA = "\e[95m"
34
+ LIGHT_CYAN = "\e[96m"
35
+
36
+ TEXT_COLORS = (constants.grep(/^[^ON_]*/) - STYLES).freeze
29
37
 
30
38
  # Escape codes for background color.
31
- ON_BLACK = "\e[40m"
32
- ON_RED = "\e[41m"
33
- ON_GREEN = "\e[42m"
34
- ON_YELLOW = "\e[43m"
35
- ON_BLUE = "\e[44m"
36
- ON_MAGENTA = "\e[45m"
37
- ON_CYAN = "\e[46m"
38
- ON_WHITE = "\e[47m"
39
-
40
- BACKGROUND_COLORS = %w[ ON_BLACK ON_RED ON_GREEN ON_YELLOW ON_BLUE ON_MAGENTA ON_CYAN ON_WHITE ].freeze
39
+ ON_BLACK = "\e[40m"
40
+ ON_RED = "\e[41m"
41
+ ON_GREEN = "\e[42m"
42
+ ON_YELLOW = "\e[43m"
43
+ ON_BLUE = "\e[44m"
44
+ ON_MAGENTA = "\e[45m"
45
+ ON_CYAN = "\e[46m"
46
+ ON_WHITE = "\e[47m"
47
+
48
+ ON_LIGHT_BLACK = "\e[100m"
49
+ ON_LIGHT_RED = "\e[101m"
50
+ ON_LIGHT_GREEN = "\e[102m"
51
+ ON_LIGHT_YELLOW = "\e[103m"
52
+ ON_LIGHT_BLUE = "\e[104m"
53
+ ON_LIGHT_MAGENTA = "\e[105m"
54
+ ON_LIGHT_CYAN = "\e[106m"
55
+
56
+ BACKGROUND_COLORS = constants.grep(/^ON_*/).freeze
41
57
 
42
58
  attr_reader :enabled
43
59
 
@@ -19,6 +19,8 @@ module TTY
19
19
 
20
20
  attr_reader :trailing
21
21
 
22
+ attr_reader :escape
23
+
22
24
  # Initialize a Truncation
23
25
  #
24
26
  # @param [String] text
@@ -36,6 +38,7 @@ module TTY
36
38
  # @option options [Symbol] :length the desired length
37
39
  # @option options [Symbol] :separator the character for splitting words
38
40
  # @option options [Symbol] :trailing the character for ending sentence
41
+ # @option options [Symbol] :escape remove ANSI escape sequences
39
42
  #
40
43
  # @api private
41
44
  def initialize(text, *args)
@@ -45,6 +48,7 @@ module TTY
45
48
  @length = args[0] unless args.empty?
46
49
  @separator = options.fetch(:separator) { nil }
47
50
  @trailing = options.fetch(:trailing) { DEFAULT_TRAILING }
51
+ @escape = options.fetch(:escape) { true }
48
52
  end
49
53
 
50
54
  # Truncate a text
@@ -56,7 +60,7 @@ module TTY
56
60
  return text unless length && length > 0
57
61
 
58
62
  as_unicode do
59
- chars = text.chars.to_a
63
+ chars = (escape ? escape_text : text).chars.to_a
60
64
  return chars.join if chars.length <= length
61
65
  stop = chars[0, length_without_trailing].rindex(separator)
62
66
 
@@ -66,6 +70,17 @@ module TTY
66
70
 
67
71
  private
68
72
 
73
+ # Strip ANSI characters from the text
74
+ #
75
+ # @param [String] text
76
+ #
77
+ # @return [String]
78
+ #
79
+ # @api private
80
+ def escape_text
81
+ TTY.terminal.color.remove text.dup
82
+ end
83
+
69
84
  # Leave space for the trailing characters
70
85
  #
71
86
  # @return [Integer]