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,167 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/printing/fields'
3
+
4
+ describe CommandKit::Printing::Fields do
5
+ module TestPrintingFields
6
+ class TestCmd
7
+
8
+ include CommandKit::Printing::Fields
9
+
10
+ end
11
+ end
12
+
13
+ let(:command_class) { TestPrintingFields::TestCmd }
14
+ subject { command_class.new }
15
+
16
+ let(:nl) { $/ }
17
+
18
+ describe "#print_fields" do
19
+ context "when given a Hash" do
20
+ context "and all key values are the same length" do
21
+ let(:name1) { 'A' }
22
+ let(:value1) { 'foo' }
23
+ let(:name2) { 'B' }
24
+ let(:value2) { 'bar' }
25
+
26
+ let(:hash) do
27
+ {
28
+ name1 => value1,
29
+ name2 => value2
30
+ }
31
+ end
32
+
33
+ it "must not left-justify the Hash keys" do
34
+ expect {
35
+ subject.print_fields(hash)
36
+ }.to output("#{name1}: #{value1}#{nl}#{name2}: #{value2}#{nl}").to_stdout
37
+ end
38
+ end
39
+
40
+ context "but key values have different lengths" do
41
+ let(:name1) { 'A' }
42
+ let(:value1) { 'foo' }
43
+ let(:name2) { 'Bar' }
44
+ let(:value2) { 'bar' }
45
+
46
+ let(:hash) do
47
+ {
48
+ name1 => value1,
49
+ name2 => value2
50
+ }
51
+ end
52
+
53
+ it "must left-justify the Hash keys" do
54
+ expect {
55
+ subject.print_fields(hash)
56
+ }.to output("#{name1}: #{value1}#{nl}#{name2}: #{value2}#{nl}").to_stdout
57
+ end
58
+ end
59
+
60
+ context "but the key values are not Strings" do
61
+ let(:name1) { 1 }
62
+ let(:value1) { 'foo' }
63
+ let(:name2) { 100 }
64
+ let(:value2) { 'bar' }
65
+
66
+ let(:hash) do
67
+ {
68
+ name1 => value1,
69
+ name2 => value2
70
+ }
71
+ end
72
+
73
+ it "must convert them to Strings before calculating justification" do
74
+ expect {
75
+ subject.print_fields(hash)
76
+ }.to output("#{name1}: #{value1}#{nl}#{name2}: #{value2}#{nl}").to_stdout
77
+ end
78
+ end
79
+ end
80
+
81
+ context "when given an Array of tuples" do
82
+ context "and all first tuple values are the same length" do
83
+ let(:name1) { 'A' }
84
+ let(:value1) { 'foo' }
85
+ let(:name2) { 'B' }
86
+ let(:value2) { 'bar' }
87
+
88
+ let(:array) do
89
+ [
90
+ [name1, value1],
91
+ [name2, value2]
92
+ ]
93
+ end
94
+
95
+ it "must not left-justify the tuples" do
96
+ expect {
97
+ subject.print_fields(array)
98
+ }.to output("#{name1}: #{value1}#{nl}#{name2}: #{value2}#{nl}").to_stdout
99
+ end
100
+ end
101
+
102
+ context "but first tuple values have different lengths" do
103
+ let(:name1) { 'A' }
104
+ let(:value1) { 'foo' }
105
+ let(:name2) { 'Bar' }
106
+ let(:value2) { 'bar' }
107
+
108
+ let(:array) do
109
+ [
110
+ [name1, value1],
111
+ [name2, value2]
112
+ ]
113
+ end
114
+
115
+ it "must left-justify the tuples" do
116
+ expect {
117
+ subject.print_fields(array)
118
+ }.to output("#{name1}: #{value1}#{nl}#{name2}: #{value2}#{nl}").to_stdout
119
+ end
120
+ end
121
+
122
+ context "but the first tuple values are not Strings" do
123
+ let(:name1) { 1 }
124
+ let(:value1) { 'foo' }
125
+ let(:name2) { 100 }
126
+ let(:value2) { 'bar' }
127
+
128
+ let(:array) do
129
+ [
130
+ [name1, value1],
131
+ [name2, value2]
132
+ ]
133
+ end
134
+
135
+ it "must convert them to Strings before calculating justification" do
136
+ expect {
137
+ subject.print_fields(array)
138
+ }.to output("#{name1}: #{value1}#{nl}#{name2}: #{value2}#{nl}").to_stdout
139
+ end
140
+ end
141
+ end
142
+
143
+ context "but the values contain multiple lines" do
144
+ let(:name1) { 'A' }
145
+ let(:value1) { 'foo' }
146
+ let(:name2) { 'Bar' }
147
+ let(:line1) { 'bar' }
148
+ let(:line2) { 'baz' }
149
+ let(:value2) do
150
+ [line1, line2].join($/)
151
+ end
152
+
153
+ let(:hash) do
154
+ {
155
+ name1 => value1,
156
+ name2 => value2
157
+ }
158
+ end
159
+
160
+ it "must print the header with the first line and then indent the other lines" do
161
+ expect {
162
+ subject.print_fields(hash)
163
+ }.to output("#{name1}: #{value1}#{nl}#{name2}: #{line1}#{nl} #{line2}#{nl}").to_stdout
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/printing/lists'
3
+
4
+ describe CommandKit::Printing::Lists do
5
+ module TestPrintingLists
6
+ class TestCmd
7
+
8
+ include CommandKit::Printing::Lists
9
+
10
+ end
11
+ end
12
+
13
+ let(:command_class) { TestPrintingLists::TestCmd }
14
+ subject { command_class.new }
15
+
16
+ describe "#print_list" do
17
+ let(:list) { %w[foo bar baz] }
18
+
19
+ it "must print each item in the list with a '*' bullet" do
20
+ expect {
21
+ subject.print_list(list)
22
+ }.to output(
23
+ list.map { |item| "* #{item}" }.join($/) + $/
24
+ ).to_stdout
25
+ end
26
+
27
+ context "when the list contins multi-line Strings" do
28
+ let(:item1) { "foo" }
29
+ let(:item2) do
30
+ [
31
+ "line 1",
32
+ "line 2",
33
+ "line 3"
34
+ ].join($/)
35
+ end
36
+ let(:item3) { "bar" }
37
+ let(:list) { [item1, item2, item3] }
38
+
39
+ it "must print the bullet with the first line and then indent the other lines" do
40
+ expect {
41
+ subject.print_list(list)
42
+ }.to output(
43
+ [
44
+ "* #{item1}",
45
+ "* #{item2.lines[0].chomp}",
46
+ " #{item2.lines[1].chomp}",
47
+ " #{item2.lines[2].chomp}",
48
+ "* #{item3}",
49
+ ''
50
+ ].join($/)
51
+ ).to_stdout
52
+ end
53
+ end
54
+
55
+ context "when the list contains nested-lists" do
56
+ let(:item1) { 'item 1' }
57
+ let(:sub_item1) { 'sub-item 1' }
58
+ let(:sub_item2) { 'sub-item 2' }
59
+ let(:item2) { 'item 2' }
60
+
61
+ let(:list) do
62
+ [
63
+ 'item 1',
64
+ [
65
+ 'sub-item 1',
66
+ 'sub-item 2'
67
+ ],
68
+ 'item 2'
69
+ ]
70
+ end
71
+
72
+ it "must indent and print each sub-list" do
73
+ expect {
74
+ subject.print_list(list)
75
+ }.to output(
76
+ [
77
+ "* #{item1}",
78
+ " * #{sub_item1}",
79
+ " * #{sub_item2}",
80
+ "* #{item2}",
81
+ ''
82
+ ].join($/)
83
+ ).to_stdout
84
+ end
85
+ end
86
+
87
+ context "when the bullet: keyowrd argument is given" do
88
+ let(:bullet) { '-' }
89
+
90
+ it "must print each item in the list with the bullet character" do
91
+ expect {
92
+ subject.print_list(list, bullet: bullet)
93
+ }.to output(
94
+ list.map { |item| "#{bullet} #{item}" }.join($/) + $/
95
+ ).to_stdout
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/printing/tables/border_style'
3
+
4
+ describe CommandKit::Printing::Tables::BorderStyle do
5
+ describe "#initialize" do
6
+ [
7
+ :top_left_corner,
8
+ :top_border,
9
+ :top_joined_border,
10
+ :top_right_corner,
11
+ :left_border,
12
+ :left_joined_border,
13
+ :horizontal_separator,
14
+ :vertical_separator,
15
+ :inner_joined_border,
16
+ :right_border,
17
+ :right_joined_border,
18
+ :bottom_border,
19
+ :bottom_left_corner,
20
+ :bottom_joined_border,
21
+ :bottom_right_corner
22
+ ].each do |keyword|
23
+ context "when #{keyword}: keyword is given" do
24
+ let(:keyword) { keyword }
25
+ let(:value) { 'x' }
26
+
27
+ subject { described_class.new(**{keyword => value}) }
28
+
29
+ it "must set ##{keyword}" do
30
+ expect(subject.send(keyword)).to eq(value)
31
+ end
32
+ end
33
+
34
+ context "when #{keyword}: keyword is not given" do
35
+ let(:keyword) { keyword }
36
+
37
+ it "must default ##{keyword} to ' '" do
38
+ expect(subject.send(keyword)).to eq(' ')
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/printing/tables/cell_builder'
3
+
4
+ describe CommandKit::Printing::Tables::CellBuilder do
5
+ describe "#initialize" do
6
+ it "must initialize #lines to an empty Array" do
7
+ expect(subject.lines).to eq([])
8
+ end
9
+
10
+ it "must default #height to 0" do
11
+ expect(subject.height).to eq(0)
12
+ end
13
+
14
+ it "must default #width to 0" do
15
+ expect(subject.width).to eq(0)
16
+ end
17
+
18
+ context "when an initial value is given" do
19
+ subject { described_class.new(value) }
20
+
21
+ context "and it's a String" do
22
+ let(:value) do
23
+ <<~EOS
24
+ foo bar
25
+ baz qux
26
+ EOS
27
+ end
28
+
29
+ it "must split the value into separate lines and populate #lines" do
30
+ expect(subject.lines).to eq(value.lines(chomp: true))
31
+ end
32
+ end
33
+
34
+ context "but it's not a String" do
35
+ let(:value) { 42 }
36
+
37
+ it "must convert it to a String before adding it to #lines" do
38
+ expect(subject.lines).to eq([value.to_s])
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ describe ".line_width" do
45
+ subject { described_class }
46
+
47
+ context "when given a plain-text String" do
48
+ let(:line) { "foo bar baz" }
49
+
50
+ it "must return the line length" do
51
+ expect(subject.line_width(line)).to eq(line.length)
52
+ end
53
+ end
54
+
55
+ context "when given a String containing ANSI control sequences" do
56
+ let(:line_without_ansi) { "foo bar baz" }
57
+ let(:line) { "\e[1m\e[32m#{line_without_ansi}\e[0m" }
58
+
59
+ it "must return the length of the line without the ANSI control sequences" do
60
+ expect(subject.line_width(line)).to eq(line_without_ansi.length)
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "#<<" do
66
+ let(:line) { "foo bar baz" }
67
+
68
+ it "must increment #height by 1" do
69
+ expect(subject.height).to eq(0)
70
+
71
+ subject << line
72
+ expect(subject.height).to eq(1)
73
+
74
+ subject << line
75
+ expect(subject.height).to eq(2)
76
+ end
77
+
78
+ it "must append the line to #lines" do
79
+ subject << line
80
+
81
+ expect(subject.lines.last).to eq(line)
82
+ end
83
+
84
+ it "must return self" do
85
+ expect(subject << line).to be(subject)
86
+ end
87
+
88
+ context "when the line's line-width is greater than #width" do
89
+ let(:previous_line) { "foo" }
90
+
91
+ it "must update #width" do
92
+ subject << previous_line
93
+ expect(subject.width).to eq(previous_line.length)
94
+
95
+ subject << line
96
+ expect(subject.width).to eq(line.length)
97
+ end
98
+ end
99
+
100
+ context "when the line's line-width is not greater than #width" do
101
+ let(:previous_line) { "foo bar baz" }
102
+ let(:line) { "foo" }
103
+
104
+ it "must update #width" do
105
+ subject << previous_line
106
+ expect(subject.width).to eq(previous_line.length)
107
+
108
+ subject << line
109
+ expect(subject.width).to eq(previous_line.length)
110
+ end
111
+ end
112
+ end
113
+
114
+ describe "#[]" do
115
+ let(:line1) { "foo bar" }
116
+ let(:line2) { "baz qux" }
117
+
118
+ before do
119
+ subject << line1
120
+ subject << line2
121
+ end
122
+
123
+ context "when the index is within the bounds of #lines" do
124
+ it "must return the line at the given index" do
125
+ expect(subject[1]).to eq(line2)
126
+ end
127
+ end
128
+
129
+ context "when the index is out of the bounds of #lines" do
130
+ it "must return ''" do
131
+ expect(subject[2]).to eq('')
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/printing/tables/row_builder'
3
+
4
+ describe CommandKit::Printing::Tables::RowBuilder do
5
+ it { expect(described_class).to include(Enumerable) }
6
+
7
+ describe "#initialize" do
8
+ it "must initialize #cells to an empty Array" do
9
+ expect(subject.cells).to eq([])
10
+ end
11
+
12
+ it "must default #height to 0" do
13
+ expect(subject.height).to eq(0)
14
+ end
15
+
16
+ it "must default #width to 0" do
17
+ expect(subject.width).to eq(0)
18
+ end
19
+
20
+ it "must default #columns to 0" do
21
+ expect(subject.columns).to eq(0)
22
+ end
23
+
24
+ context "when given initial cells" do
25
+ let(:cell1) { "foo bar\nbaz qux" }
26
+ let(:cell2) { 'aaaa bbbb' }
27
+ let(:cells) { [cell1, cell2] }
28
+
29
+ subject { described_class.new(cells) }
30
+
31
+ it "must append the cells to the row" do
32
+ expect(subject.cells[0]).to be_kind_of(CommandKit::Printing::Tables::CellBuilder)
33
+ expect(subject.cells[0].lines).to eq(cell1.lines(chomp: true))
34
+
35
+ expect(subject.cells[1]).to be_kind_of(CommandKit::Printing::Tables::CellBuilder)
36
+ expect(subject.cells[1].lines).to eq(cell2.lines(chomp: true))
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "#<<" do
42
+ context "when given a non-nil value" do
43
+ let(:value) { "foo bar\nbaz qux" }
44
+
45
+ it "must append a new CommandKit::Printing::Tables::CellBuilder object to #cells" do
46
+ subject << value
47
+
48
+ expect(subject.cells.last).to be_kind_of(CommandKit::Printing::Tables::CellBuilder)
49
+ expect(subject.cells.last.lines).to eq(value.lines(chomp: true))
50
+ end
51
+
52
+ context "when the new cell's height is greater than the row's current #height" do
53
+ let(:value1) { "foo" }
54
+ let(:value2) { "bar\nbaz\nqux" }
55
+
56
+ it "must update the row's current #height" do
57
+ expect(subject.height).to eq(0)
58
+
59
+ subject << value1
60
+ expect(subject.height).to eq(subject.cells[0].height)
61
+
62
+ subject << value2
63
+ expect(subject.height).to eq(subject.cells[1].height)
64
+ end
65
+ end
66
+
67
+ context "when the new cell's height is not greater than the row's current #height" do
68
+ let(:value1) { "bar\nbaz\nqux" }
69
+ let(:value2) { "foo" }
70
+
71
+ it "must not update the row's current #height" do
72
+ expect(subject.height).to eq(0)
73
+
74
+ subject << value1
75
+ expect(subject.height).to eq(subject.cells[0].height)
76
+
77
+ subject << value2
78
+ expect(subject.height).to eq(subject.cells[0].height)
79
+ end
80
+ end
81
+
82
+ it "must add the cell value's line width to the row's #width" do
83
+ value1 = "foo"
84
+ value1_width = CommandKit::Printing::Tables::CellBuilder.line_width(value1)
85
+
86
+ expect(subject.width).to eq(0)
87
+
88
+ subject << value1
89
+ expect(subject.width).to eq(value1_width)
90
+
91
+ value2 = "\e[32mbar\e[0m"
92
+ value2_width = CommandKit::Printing::Tables::CellBuilder.line_width(value2)
93
+
94
+ subject << value2
95
+ expect(subject.width).to eq(value1_width + value2_width)
96
+ end
97
+
98
+ it "must incrmenet #columns by 1" do
99
+ expect(subject.columns).to eq(0)
100
+
101
+ subject << "foo"
102
+ expect(subject.columns).to eq(1)
103
+
104
+ subject << "bar"
105
+ expect(subject.columns).to eq(2)
106
+ end
107
+
108
+ it "must return self" do
109
+ expect(subject << value).to be(subject)
110
+ end
111
+ end
112
+
113
+ context "when given a nil value" do
114
+ it "must append #{described_class}::EMPTY_CELL to #cells" do
115
+ subject << nil
116
+
117
+ expect(subject.cells.last).to be(described_class::EMPTY_CELL)
118
+ end
119
+
120
+ it "must return self" do
121
+ expect(subject << nil).to be(subject)
122
+ end
123
+ end
124
+ end
125
+
126
+ describe "#[]" do
127
+ before do
128
+ subject << "foo bar"
129
+ subject << "baz qux"
130
+ end
131
+
132
+ context "when the given column index is within the bounds of #cells" do
133
+ it "must return the cell at the given index" do
134
+ expect(subject[1]).to be(subject.cells[1])
135
+ end
136
+ end
137
+
138
+ context "when the given column index is not within the bounds of #cells" do
139
+ it "must return #{described_class}::EMPTY_CELL" do
140
+ expect(subject[2]).to be(described_class::EMPTY_CELL)
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "#each" do
146
+ before do
147
+ subject << "foo bar"
148
+ subject << "baz qux"
149
+ end
150
+
151
+ context "when given a block" do
152
+ it "must yield each cell in #cells" do
153
+ expect { |b|
154
+ subject.each(&b)
155
+ }.to yield_successive_args(*subject.cells)
156
+ end
157
+ end
158
+
159
+ context "when no block is given" do
160
+ it "must return an Enumerator for #cells" do
161
+ expect(subject.each.to_a).to eq(subject.cells)
162
+ end
163
+ end
164
+ end
165
+ end