command_kit 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +4 -5
  3. data/.rubocop.yml +14 -1
  4. data/ChangeLog.md +82 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +18 -9
  8. data/command_kit.gemspec +0 -1
  9. data/examples/printing/tables.rb +141 -0
  10. data/gemspec.yml +3 -3
  11. data/lib/command_kit/arguments/argument.rb +2 -2
  12. data/lib/command_kit/arguments.rb +27 -2
  13. data/lib/command_kit/bug_report.rb +105 -0
  14. data/lib/command_kit/colors.rb +488 -15
  15. data/lib/command_kit/command.rb +1 -2
  16. data/lib/command_kit/edit.rb +54 -0
  17. data/lib/command_kit/env.rb +1 -1
  18. data/lib/command_kit/file_utils.rb +46 -0
  19. data/lib/command_kit/options/option.rb +45 -22
  20. data/lib/command_kit/options/option_value.rb +2 -2
  21. data/lib/command_kit/options/parser.rb +1 -4
  22. data/lib/command_kit/options/quiet.rb +1 -1
  23. data/lib/command_kit/options/verbose.rb +2 -2
  24. data/lib/command_kit/options/version.rb +10 -0
  25. data/lib/command_kit/options.rb +89 -14
  26. data/lib/command_kit/os.rb +1 -1
  27. data/lib/command_kit/printing/fields.rb +56 -0
  28. data/lib/command_kit/printing/indent.rb +1 -1
  29. data/lib/command_kit/printing/lists.rb +91 -0
  30. data/lib/command_kit/printing/tables/border_style.rb +169 -0
  31. data/lib/command_kit/printing/tables/cell_builder.rb +93 -0
  32. data/lib/command_kit/printing/tables/row_builder.rb +111 -0
  33. data/lib/command_kit/printing/tables/style.rb +198 -0
  34. data/lib/command_kit/printing/tables/table_builder.rb +145 -0
  35. data/lib/command_kit/printing/tables/table_formatter.rb +254 -0
  36. data/lib/command_kit/printing/tables.rb +208 -0
  37. data/lib/command_kit/program_name.rb +9 -0
  38. data/lib/command_kit/stdio.rb +5 -1
  39. data/lib/command_kit/version.rb +1 -1
  40. data/spec/arguments_spec.rb +33 -0
  41. data/spec/bug_report_spec.rb +266 -0
  42. data/spec/colors_spec.rb +232 -195
  43. data/spec/command_name_spec.rb +1 -1
  44. data/spec/command_spec.rb +2 -2
  45. data/spec/edit_spec.rb +72 -0
  46. data/spec/file_utils_spec.rb +59 -0
  47. data/spec/fixtures/template.erb +5 -0
  48. data/spec/options/option_spec.rb +48 -2
  49. data/spec/options/parser_spec.rb +0 -10
  50. data/spec/options/quiet_spec.rb +51 -0
  51. data/spec/options/verbose_spec.rb +51 -0
  52. data/spec/options/version_spec.rb +146 -0
  53. data/spec/options_spec.rb +46 -0
  54. data/spec/pager_spec.rb +1 -1
  55. data/spec/printing/fields_spec.rb +167 -0
  56. data/spec/printing/lists_spec.rb +99 -0
  57. data/spec/printing/tables/border_style.rb +43 -0
  58. data/spec/printing/tables/cell_builer_spec.rb +135 -0
  59. data/spec/printing/tables/row_builder_spec.rb +165 -0
  60. data/spec/printing/tables/style_spec.rb +377 -0
  61. data/spec/printing/tables/table_builder_spec.rb +252 -0
  62. data/spec/printing/tables/table_formatter_spec.rb +1180 -0
  63. data/spec/printing/tables_spec.rb +1069 -0
  64. data/spec/program_name_spec.rb +8 -0
  65. metadata +36 -7
@@ -0,0 +1,169 @@
1
+ module CommandKit
2
+ module Printing
3
+ module Tables
4
+ #
5
+ # Represents the table's border style.
6
+ #
7
+ # @api private
8
+ #
9
+ class BorderStyle
10
+
11
+ # The top-left-corner border character.
12
+ #
13
+ # @return [String]
14
+ attr_reader :top_left_corner
15
+
16
+ # The top-border character.
17
+ #
18
+ # @return [String]
19
+ attr_reader :top_border
20
+
21
+ # The top-joined-border character.
22
+ #
23
+ # @return [String]
24
+ attr_reader :top_joined_border
25
+
26
+ # The top-right-corner border character.
27
+ #
28
+ # @return [String]
29
+ attr_reader :top_right_corner
30
+
31
+ # The left-hand-side border character.
32
+ #
33
+ # @return [String]
34
+ attr_reader :left_border
35
+
36
+ # The left-hand-side-joined-border character.
37
+ #
38
+ # @return [String]
39
+ attr_reader :left_joined_border
40
+
41
+ # The horizontal-separator character.
42
+ #
43
+ # @return [String]
44
+ attr_reader :horizontal_separator
45
+
46
+ # The vertical-separator character.
47
+ #
48
+ # @return [String]
49
+ attr_reader :vertical_separator
50
+
51
+ # The inner-joined border character.
52
+ #
53
+ # @return [String]
54
+ attr_reader :inner_joined_border
55
+
56
+ # The right-hand-side border character.
57
+ #
58
+ # @return [String]
59
+ attr_reader :right_border
60
+
61
+ # The right-hand-side joined border character.
62
+ #
63
+ # @return [String]
64
+ attr_reader :right_joined_border
65
+
66
+ # The bottom border character.
67
+ #
68
+ # @return [String]
69
+ attr_reader :bottom_border
70
+
71
+ # The bottom-left-corner border character.
72
+ #
73
+ # @return [String]
74
+ attr_reader :bottom_left_corner
75
+
76
+ # The bottom-joined border character.
77
+ #
78
+ # @return [String]
79
+ attr_reader :bottom_joined_border
80
+
81
+ # The bottom-right-corner border character.
82
+ #
83
+ # @return [String]
84
+ attr_reader :bottom_right_corner
85
+
86
+ #
87
+ # Initializes the border style.
88
+ #
89
+ # @param [String] top_left_corner
90
+ # The top-left-corner border character.
91
+ #
92
+ # @param [String] top_border
93
+ # The top-border character.
94
+ #
95
+ # @param [String] top_joined_border
96
+ # The top-joined-border character.
97
+ #
98
+ # @param [String] top_right_corner
99
+ # The top-right-corner border character.
100
+ #
101
+ # @param [String] left_border
102
+ # The left-hand-side border character.
103
+ #
104
+ # @param [String] left_joined_border
105
+ # The left-hand-side-joined-border character.
106
+ #
107
+ # @param [String] horizontal_separator
108
+ # The horizontal-separator character.
109
+ #
110
+ # @param [String] vertical_separator
111
+ # The vertical-separator character.
112
+ #
113
+ # @param [String] inner_joined_border
114
+ # The inner-joined border character.
115
+ #
116
+ # @param [String] right_border
117
+ # The right-hand-side border character.
118
+ #
119
+ # @param [String] right_joined_border
120
+ # The right-hand-side joined border character.
121
+ #
122
+ # @param [String] bottom_border
123
+ # The bottom border character.
124
+ #
125
+ # @param [String] bottom_left_corner
126
+ # The bottom-left-corner border character.
127
+ #
128
+ # @param [String] bottom_joined_border
129
+ # The bottom-joined border character.
130
+ #
131
+ # @param [String] bottom_right_corner
132
+ # The bottom-right-corner border character.
133
+ #
134
+ def initialize(top_left_corner: ' ',
135
+ top_border: ' ',
136
+ top_joined_border: ' ',
137
+ top_right_corner: ' ',
138
+ left_border: ' ',
139
+ left_joined_border: ' ',
140
+ horizontal_separator: ' ',
141
+ vertical_separator: ' ',
142
+ inner_joined_border: ' ',
143
+ right_border: ' ',
144
+ right_joined_border: ' ',
145
+ bottom_border: ' ',
146
+ bottom_left_corner: ' ',
147
+ bottom_joined_border: ' ',
148
+ bottom_right_corner: ' ')
149
+ @top_left_corner = top_left_corner
150
+ @top_border = top_border
151
+ @top_joined_border = top_joined_border
152
+ @top_right_corner = top_right_corner
153
+ @left_border = left_border
154
+ @left_joined_border = left_joined_border
155
+ @horizontal_separator = horizontal_separator
156
+ @vertical_separator = vertical_separator
157
+ @inner_joined_border = inner_joined_border
158
+ @right_border = right_border
159
+ @right_joined_border = right_joined_border
160
+ @bottom_border = bottom_border
161
+ @bottom_left_corner = bottom_left_corner
162
+ @bottom_joined_border = bottom_joined_border
163
+ @bottom_right_corner = bottom_right_corner
164
+ end
165
+
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandKit
4
+ module Printing
5
+ module Tables
6
+ #
7
+ # Build's a cell and calculates it's dimensions.
8
+ #
9
+ # @api private
10
+ #
11
+ class CellBuilder
12
+
13
+ # The lines within the cell.
14
+ #
15
+ # @return [Array<String>]
16
+ attr_reader :lines
17
+
18
+ # The height (in lines) of the cell.
19
+ #
20
+ # @return [Integer]
21
+ attr_reader :height
22
+
23
+ # The with (in characters) of the cell.
24
+ #
25
+ # @return [Integer]
26
+ attr_reader :width
27
+
28
+ #
29
+ # Initializes the cell.
30
+ #
31
+ # @param [#to_s] value
32
+ # The value for the cell.
33
+ #
34
+ def initialize(value=nil)
35
+ @lines = []
36
+ @height = 0
37
+ @width = 0
38
+
39
+ if value
40
+ value.to_s.each_line(chomp: true) do |line|
41
+ self << line
42
+ end
43
+ end
44
+ end
45
+
46
+ #
47
+ # Calculates the width of a string, sans any ASNI escape sequences.
48
+ #
49
+ # @param [String] string
50
+ # The string to calculate the width of.
51
+ #
52
+ # @return [Integer]
53
+ # The display width of the string.
54
+ #
55
+ def self.line_width(string)
56
+ string.gsub(/\e\[\d+m/,'').length
57
+ end
58
+
59
+ #
60
+ # Adds a line to the cell.
61
+ #
62
+ # @param [String] line
63
+ # A line to add to the cell.
64
+ #
65
+ # @return [self]
66
+ #
67
+ def <<(line)
68
+ line_width = self.class.line_width(line)
69
+
70
+ @height += 1
71
+ @width = line_width if line_width > @width
72
+
73
+ @lines << line
74
+ return self
75
+ end
76
+
77
+ #
78
+ # Fetches a line from the cell.
79
+ #
80
+ # @param [Integer] line_index
81
+ # The line index to fetch.
82
+ #
83
+ # @return [String]
84
+ # The line at the line index or an empty String if the cell is empty.
85
+ #
86
+ def [](line_index)
87
+ @lines.fetch(line_index,'')
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,111 @@
1
+ require 'command_kit/printing/tables/cell_builder'
2
+
3
+ module CommandKit
4
+ module Printing
5
+ module Tables
6
+ #
7
+ # Builds a table row and calculates it's dimensions.
8
+ #
9
+ # @api private
10
+ #
11
+ class RowBuilder
12
+
13
+ include Enumerable
14
+
15
+ # The cells within the row.
16
+ #
17
+ # @return [Array<CellBuilder>]
18
+ attr_reader :cells
19
+
20
+ # The height (in lines) for the row.
21
+ #
22
+ # @return [Integer]
23
+ attr_reader :height
24
+
25
+ # The width (in characters) for the row.
26
+ #
27
+ # @return [Integer]
28
+ attr_reader :width
29
+
30
+ # The number of columns in the row.
31
+ #
32
+ # @return [Integer]
33
+ attr_reader :columns
34
+
35
+ #
36
+ # Initializes the row.
37
+ #
38
+ # @param [Array, nil] cells
39
+ # The cells for the row.
40
+ #
41
+ def initialize(cells=nil)
42
+ @cells = []
43
+
44
+ @height = 0
45
+ @width = 0
46
+
47
+ @columns = 0
48
+
49
+ if cells
50
+ cells.each { |value| self << value }
51
+ end
52
+ end
53
+
54
+ # An empty cell.
55
+ EMPTY_CELL = CellBuilder.new
56
+
57
+ #
58
+ # Appends a value to the row.
59
+ #
60
+ # @param [#to_s] value
61
+ # The cell value to add to the row.
62
+ #
63
+ # @return [self]
64
+ #
65
+ def <<(value)
66
+ new_cell = if value then CellBuilder.new(value)
67
+ else EMPTY_CELL
68
+ end
69
+
70
+ @height = new_cell.height if new_cell.height > @height
71
+ @width += new_cell.width
72
+ @columns += 1
73
+
74
+ @cells << new_cell
75
+ return self
76
+ end
77
+
78
+ #
79
+ # Fetches a cell from the row.
80
+ #
81
+ # @param [Integer] column_index
82
+ # The column index.
83
+ #
84
+ # @return [CellBuilder]
85
+ # The cell at the given column index or an empty cell if the row
86
+ # does not have a cell at the given column index.
87
+ #
88
+ def [](column_index)
89
+ @cells.fetch(column_index,EMPTY_CELL)
90
+ end
91
+
92
+ #
93
+ # Enumerates over each cell in the row.
94
+ #
95
+ # @yield [cell]
96
+ # The given block will be passed each cell within the row.
97
+ #
98
+ # @yieldparam [CellBuilder] cell
99
+ # A cell within the row.
100
+ #
101
+ # @return [Enumerator]
102
+ # If no block is given, an Enumerator will be returned.
103
+ #
104
+ def each(&block)
105
+ @cells.each(&block)
106
+ end
107
+
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'command_kit/printing/tables/border_style'
4
+
5
+ module CommandKit
6
+ module Printing
7
+ module Tables
8
+ #
9
+ # Contains the table's style configuration.
10
+ #
11
+ # @api private
12
+ #
13
+ class Style
14
+
15
+ # Built-in border styles.
16
+ BORDER_STYLES = {
17
+ ascii: BorderStyle.new(
18
+ top_left_corner: '+',
19
+ top_border: '-',
20
+ top_joined_border: '+',
21
+ top_right_corner: '+',
22
+ left_border: '|',
23
+ left_joined_border: '+',
24
+ horizontal_separator: '-',
25
+ vertical_separator: '|',
26
+ inner_joined_border: '+',
27
+ right_border: '|',
28
+ right_joined_border: '+',
29
+ bottom_border: '-',
30
+ bottom_left_corner: '+',
31
+ bottom_joined_border: '+',
32
+ bottom_right_corner: '+'
33
+ ),
34
+
35
+ line: BorderStyle.new(
36
+ top_left_corner: '┌',
37
+ top_border: '─',
38
+ top_joined_border: '┬',
39
+ top_right_corner: '┐',
40
+ left_border: '│',
41
+ left_joined_border: '├',
42
+ horizontal_separator: '─',
43
+ vertical_separator: '│',
44
+ inner_joined_border: '┼',
45
+ right_border: '│',
46
+ right_joined_border: '┤',
47
+ bottom_border: '─',
48
+ bottom_left_corner: '└',
49
+ bottom_joined_border: '┴',
50
+ bottom_right_corner: '┘'
51
+ ),
52
+
53
+ double_line: BorderStyle.new(
54
+ top_left_corner: '╔',
55
+ top_border: '═',
56
+ top_joined_border: '╦',
57
+ top_right_corner: '╗',
58
+ left_border: '║',
59
+ left_joined_border: '╠',
60
+ horizontal_separator: '═',
61
+ vertical_separator: '║',
62
+ inner_joined_border: '╬',
63
+ right_border: '║',
64
+ right_joined_border: '╣',
65
+ bottom_border: '═',
66
+ bottom_left_corner: '╚',
67
+ bottom_joined_border: '╩',
68
+ bottom_right_corner: '╝'
69
+ )
70
+ }
71
+
72
+ # The border style.
73
+ #
74
+ # @return [BorderStyle]
75
+ attr_reader :border
76
+
77
+ # The padding to use for cells.
78
+ #
79
+ # @return [Integer]
80
+ attr_reader :padding
81
+
82
+ # The justification to use for cells.
83
+ #
84
+ # @return [:left, :right, :center]
85
+ attr_reader :justify
86
+
87
+ # The justification to use for header cells.
88
+ #
89
+ # @return [:left, :right, :center]
90
+ attr_reader :justify_header
91
+
92
+ # Specifies whether to separate rows with a border row.
93
+ #
94
+ # @return [Boolean]
95
+ attr_reader :separate_rows
96
+
97
+ #
98
+ # Initializes the style.
99
+ #
100
+ # @param [:line, :double_line, nil, Hash{Symbol => String}, :ascii] border
101
+ # The border style or a custom Hash of border characters.
102
+ #
103
+ # @option border [String] :top_left_corner (' ')
104
+ # The top-left-corner border character.
105
+ #
106
+ # @option border [String] :top_border (' ')
107
+ # The top-border character.
108
+ #
109
+ # @option border [String] :top_joined_border (' ')
110
+ # The top-joined-border character.
111
+ #
112
+ # @option border [String] :top_right_corner (' ')
113
+ # The top-right-corner border character.
114
+ #
115
+ # @option border [String] :left_border (' ')
116
+ # The left-hand-side border character.
117
+ #
118
+ # @option border [String] :left_joined_border (' ')
119
+ # The left-hand-side-joined-border character.
120
+ #
121
+ # @option border [String] :horizontal_separator (' ')
122
+ # The horizontal-separator character.
123
+ #
124
+ # @option border [String] :vertical_separator (' ')
125
+ # The vertical-separator character.
126
+ #
127
+ # @option border [String] :inner_joined_border (' ')
128
+ # The inner-joined border character.
129
+ #
130
+ # @option border [String] :right_border (' ')
131
+ # The right-hand-side border character.
132
+ #
133
+ # @option border [String] :right_joined_border (' ')
134
+ # The right-hand-side joined border character.
135
+ #
136
+ # @option border [String] :bottom_border (' ')
137
+ # The bottom border character.
138
+ #
139
+ # @option border [String] :bottom_left_corner (' ')
140
+ # The bottom-left-corner border character.
141
+ #
142
+ # @option border [String] :bottom_joined_border (' ')
143
+ # The bottom-joined border character.
144
+ #
145
+ # @option border [String] :bottom_right_corner (' ')
146
+ # The bottom-right-corner border character.
147
+ #
148
+ # @param [Integer] padding
149
+ # The number of characters to pad table cell values with.
150
+ #
151
+ # @param [:left, :right, :center] justify
152
+ # Specifies how to justify the table cell values.
153
+ #
154
+ # @param [:left, :right, :center] justify_header
155
+ # Specifies how to justify the table header cell values.
156
+ #
157
+ # @param [Boolean] separate_rows
158
+ # Specifies whether to add separator rows in between the rows.
159
+ #
160
+ def initialize(border: nil,
161
+ padding: 1,
162
+ justify: :left,
163
+ justify_header: :center,
164
+ separate_rows: false)
165
+ @border = case border
166
+ when Hash
167
+ BorderStyle.new(**border)
168
+ when Symbol
169
+ BORDER_STYLES.fetch(border) do
170
+ raise(ArgumentError,"unknown border style (#{border.inspect}) must be either #{BORDER_STYLES.keys.map(&:inspect).join(', ')}")
171
+ end
172
+ when nil then nil
173
+ else
174
+ raise(ArgumentError,"invalid border value (#{border.inspect}) must be either #{BORDER_STYLES.keys.map(&:inspect).join(', ')}, Hash, or nil")
175
+ end
176
+
177
+ @padding = padding
178
+
179
+ @justify = justify
180
+ @justify_header = justify_header
181
+
182
+ @separate_rows = separate_rows
183
+ end
184
+
185
+ #
186
+ # Determines if the rows should be separated.
187
+ #
188
+ # @return [Boolean]
189
+ # Specifies whether each row should be separated with a separator row.
190
+ #
191
+ def separate_rows?
192
+ @separate_rows
193
+ end
194
+
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,145 @@
1
+ require 'command_kit/printing/tables/row_builder'
2
+
3
+ module CommandKit
4
+ module Printing
5
+ module Tables
6
+ #
7
+ # Builds a table and calculates it's dimensions.
8
+ #
9
+ # @api private
10
+ #
11
+ class TableBuilder
12
+
13
+ include Enumerable
14
+
15
+ # The rows within the table.
16
+ #
17
+ # @return [Array<RowBuilder>]
18
+ attr_reader :rows
19
+
20
+ # Indicates whether the table has a header row.
21
+ #
22
+ # @return [Boolean]
23
+ attr_reader :header
24
+
25
+ # The height (in lines) of the table.
26
+ #
27
+ # @return [Integer]
28
+ attr_reader :height
29
+
30
+ # The width (in characters) of the table.
31
+ #
32
+ # @return [Integer]
33
+ attr_reader :width
34
+
35
+ # The maximum number of rows in the table.
36
+ #
37
+ # @return [Integer]
38
+ attr_reader :max_rows
39
+
40
+ # The maximum number of columns in the table.
41
+ #
42
+ # @return [Integer]
43
+ attr_reader :max_columns
44
+
45
+ # The widths of the columns in the table.
46
+ #
47
+ # @return [Array<Integer>]
48
+ attr_reader :column_widths
49
+
50
+ #
51
+ # Initializes the table.
52
+ #
53
+ # @param [Array<Array>] rows
54
+ # The rows for the table.
55
+ #
56
+ # @param [Array] header
57
+ # The header row.
58
+ #
59
+ def initialize(rows=[], header: nil)
60
+ @rows = []
61
+ @height = 0
62
+ @width = 0
63
+ @column_widths = []
64
+
65
+ @max_rows = 0
66
+ @max_columns = 0
67
+
68
+ @header = !header.nil?
69
+
70
+ self << header if header
71
+ rows.each { |row| self << row }
72
+ end
73
+
74
+ #
75
+ # Determines whether the table has a header row.
76
+ #
77
+ # @return [Boolean]
78
+ # Indicates whether the first row of the table is a header row.
79
+ #
80
+ def header?
81
+ @header
82
+ end
83
+
84
+ #
85
+ # Appends a row to the table.
86
+ #
87
+ # @param [Array] row
88
+ # The row to append.
89
+ #
90
+ # @return [self]
91
+ #
92
+ def <<(row)
93
+ new_row = RowBuilder.new(row)
94
+
95
+ new_row.each_with_index do |cell,index|
96
+ column_width = @column_widths[index] || 0
97
+
98
+ if cell.width > column_width
99
+ @column_widths[index] = cell.width
100
+ end
101
+ end
102
+
103
+ @height += new_row.height
104
+ @width = new_row.width if new_row.width > @width
105
+
106
+ @max_rows += 1
107
+ @max_columns = new_row.columns if new_row.columns > @max_columns
108
+
109
+ @rows << new_row
110
+ return self
111
+ end
112
+
113
+ #
114
+ # Fetches a row from the table.
115
+ #
116
+ # @param [Integer] row_index
117
+ # The row index to fetch the row at.
118
+ #
119
+ # @return [RowBuilder, nil]
120
+ # The row or `nil` if no row exists at the given row index.
121
+ #
122
+ def [](row_index)
123
+ @rows[row_index]
124
+ end
125
+
126
+ #
127
+ # Enumerates over each row in the table.
128
+ #
129
+ # @yield [row]
130
+ # If a block is given, it will be passed each row within the table.
131
+ #
132
+ # @yieldparam [RowBuilder] row
133
+ # A row within the table.
134
+ #
135
+ # @return [Enumerator]
136
+ # If no block is given, an Enumerator will be returned.
137
+ #
138
+ def each(&block)
139
+ @rows.each(&block)
140
+ end
141
+
142
+ end
143
+ end
144
+ end
145
+ end