command_kit 0.2.2 → 0.4.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 (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