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
data/spec/edit_spec.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/edit'
3
+
4
+ describe CommandKit::Edit do
5
+ module TestEdit
6
+ class TestCommand
7
+ include CommandKit::Edit
8
+ end
9
+ end
10
+
11
+ let(:command_class) { TestEdit::TestCommand }
12
+
13
+ it "must also include CommandKit::Env" do
14
+ expect(command_class).to include(CommandKit::Env)
15
+ end
16
+
17
+ describe "#editor" do
18
+ subject { command_class.new(env: env) }
19
+
20
+ context "when env['EDITOR'] is set" do
21
+ let(:editor) { 'vim' }
22
+ let(:env) do
23
+ {'EDITOR' => editor}
24
+ end
25
+
26
+ it "must return env['EDITOR']" do
27
+ expect(subject.editor).to eq(env['EDITOR'])
28
+ end
29
+ end
30
+
31
+ context "when env['EDITOR'] is not set" do
32
+ let(:env) do
33
+ {}
34
+ end
35
+
36
+ it "must return 'nano'" do
37
+ expect(subject.editor).to eq('nano')
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "#edit" do
43
+ subject { command_class.new(env: env) }
44
+
45
+ let(:arguments) { ['file.txt'] }
46
+
47
+ context "when env['EDITOR'] is set" do
48
+ let(:editor) { 'vim' }
49
+ let(:env) do
50
+ {'EDITOR' => editor}
51
+ end
52
+
53
+ it "must invoke system with #editor and the additional arguments" do
54
+ expect(subject).to receive(:system).with(subject.editor,*arguments)
55
+
56
+ subject.edit(*arguments)
57
+ end
58
+ end
59
+
60
+ context "when env['EDITOR'] is not set" do
61
+ let(:env) do
62
+ {}
63
+ end
64
+
65
+ it "must invoke system with 'nano' and the additional arguments" do
66
+ expect(subject).to receive(:system).with('nano',*arguments)
67
+
68
+ subject.edit(*arguments)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/file_utils'
3
+ require 'tempfile'
4
+
5
+ describe CommandKit::FileUtils do
6
+ module TestFileUtils
7
+ class Command
8
+ include CommandKit::FileUtils
9
+
10
+ attr_reader :var
11
+
12
+ def initialize(var: nil)
13
+ super()
14
+
15
+ @var = var
16
+ end
17
+
18
+ def test_method
19
+ 'some method'
20
+ end
21
+ end
22
+ end
23
+
24
+ let(:var) { 42 }
25
+
26
+ let(:command_class) { TestFileUtils::Command }
27
+ subject { command_class.new(var: var) }
28
+
29
+ let(:fixtures_dir) { File.expand_path(File.join(__dir__,'fixtures')) }
30
+ let(:template_path) { File.join(fixtures_dir,'template.erb') }
31
+
32
+ describe "#erb" do
33
+ let(:expected_result) do
34
+ [
35
+ "Raw text",
36
+ '',
37
+ "variable = #{var}",
38
+ '',
39
+ "method = #{subject.test_method}"
40
+ ].join($/)
41
+ end
42
+
43
+ context "when only given a source file argument" do
44
+ it "must render the erb template and return the result" do
45
+ expect(subject.erb(template_path)).to eq(expected_result)
46
+ end
47
+ end
48
+
49
+ context "when given an additional destination file argument" do
50
+ let(:dest_path) { Tempfile.new.path }
51
+
52
+ before { subject.erb(template_path,dest_path) }
53
+
54
+ it "must render the erb template and write the result to the destination" do
55
+ expect(File.read(dest_path)).to eq(expected_result)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ Raw text
2
+
3
+ variable = <%= @var %>
4
+
5
+ method = <%= test_method -%>
@@ -6,10 +6,12 @@ describe CommandKit::Options::Option do
6
6
  let(:short) { nil }
7
7
  let(:long) { '--foo' }
8
8
  let(:equals) { false }
9
+ let(:value_usage) { 'FOO' }
10
+ let(:value_required) { true }
9
11
  let(:value) do
10
12
  {
11
- usage: 'FOO',
12
- required: true
13
+ usage: value_usage,
14
+ required: value_required
13
15
  }
14
16
  end
15
17
  let(:desc) { 'Foo option' }
@@ -145,6 +147,20 @@ describe CommandKit::Options::Option do
145
147
  end
146
148
  end
147
149
 
150
+ it "must default #category to nil" do
151
+ expect(subject.category).to be(nil)
152
+ end
153
+
154
+ context "when the category: keyword is given" do
155
+ let(:category) { 'Other Options' }
156
+
157
+ subject { described_class.new(name, desc: desc, category: category) }
158
+
159
+ it "must set #category" do
160
+ expect(subject.category).to eq(category)
161
+ end
162
+ end
163
+
148
164
  context "when a block is given" do
149
165
  subject { described_class.new(name, desc: desc, &block) }
150
166
 
@@ -214,6 +230,14 @@ describe CommandKit::Options::Option do
214
230
  it "must return '--option=USAGE'" do
215
231
  expect(subject.usage.last).to eq("#{long}=#{subject.value.usage}")
216
232
  end
233
+
234
+ context "but the #value is also optional?" do
235
+ let(:value_required) { false }
236
+
237
+ it "must return '--option[=USAGE]'" do
238
+ expect(subject.usage.last).to eq("#{long}[=#{subject.value.usage}]")
239
+ end
240
+ end
217
241
  end
218
242
  end
219
243
 
@@ -293,5 +317,27 @@ describe CommandKit::Options::Option do
293
317
  expect(subject.desc).to eq("#{desc} (Default: #{default})")
294
318
  end
295
319
  end
320
+
321
+ context "when #desc was initialized with an Array" do
322
+ let(:desc) do
323
+ [
324
+ 'Line 1',
325
+ 'Line 2'
326
+ ]
327
+ end
328
+
329
+ it "must return the desc: value" do
330
+ expect(subject.desc).to eq(desc)
331
+ end
332
+
333
+ context "when #value has been initialized with a default value" do
334
+ let(:default) { "foo" }
335
+ let(:value) { {default: default} }
336
+
337
+ it "should append '(Default: ...)' to the desc Array" do
338
+ expect(subject.desc).to eq([*desc, "(Default: #{default})"])
339
+ end
340
+ end
341
+ end
296
342
  end
297
343
  end
@@ -34,16 +34,6 @@ describe CommandKit::Options::Parser do
34
34
  expect(subject.option_parser.banner).to eq("Usage: #{subject.usage}")
35
35
  end
36
36
 
37
- it "must include a 'Options:' separator" do
38
- expect(subject.option_parser.to_s).to include(
39
- [
40
- '',
41
- 'Options:',
42
- ''
43
- ].join($/)
44
- )
45
- end
46
-
47
37
  it "must define a default --help option" do
48
38
  expect(subject.option_parser.to_s).to include(
49
39
  [
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/options/quiet'
3
+
4
+ describe CommandKit::Options::Quiet do
5
+ module TestOptionsQuiet
6
+ class TestCommand
7
+ include CommandKit::Options::Quiet
8
+ end
9
+ end
10
+
11
+ let(:command_class) { TestOptionsQuiet::TestCommand }
12
+
13
+ describe ".included" do
14
+ subject { command_class }
15
+
16
+ it "must include CommandKit::Options" do
17
+ expect(subject).to include(CommandKit::Options)
18
+ end
19
+
20
+ it "must define a quiet option" do
21
+ expect(subject.options[:quiet]).to_not be(nil)
22
+ expect(subject.options[:quiet].short).to eq('-q')
23
+ expect(subject.options[:quiet].long).to eq('--quiet')
24
+ expect(subject.options[:quiet].desc).to eq('Enables quiet output')
25
+ end
26
+ end
27
+
28
+ subject { command_class.new }
29
+
30
+ describe "#quiet?" do
31
+ context "when @quiet is true" do
32
+ before do
33
+ subject.instance_variable_set('@quiet',true)
34
+ end
35
+
36
+ it "must return true" do
37
+ expect(subject.quiet?).to be(true)
38
+ end
39
+ end
40
+
41
+ context "when @quiet is false" do
42
+ before do
43
+ subject.instance_variable_set('@quiet',false)
44
+ end
45
+
46
+ it "must return false" do
47
+ expect(subject.quiet?).to be(false)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/options/verbose'
3
+
4
+ describe CommandKit::Options::Verbose do
5
+ module TestOptionsVerbose
6
+ class TestCommand
7
+ include CommandKit::Options::Verbose
8
+ end
9
+ end
10
+
11
+ let(:command_class) { TestOptionsVerbose::TestCommand }
12
+
13
+ describe ".included" do
14
+ subject { command_class }
15
+
16
+ it "must include CommandKit::Options" do
17
+ expect(subject).to include(CommandKit::Options)
18
+ end
19
+
20
+ it "must define a verbose option" do
21
+ expect(subject.options[:verbose]).to_not be(nil)
22
+ expect(subject.options[:verbose].short).to eq('-v')
23
+ expect(subject.options[:verbose].long).to eq('--verbose')
24
+ expect(subject.options[:verbose].desc).to eq('Enables verbose output')
25
+ end
26
+ end
27
+
28
+ subject { command_class.new }
29
+
30
+ describe "#verbose?" do
31
+ context "when @verbose is true" do
32
+ before do
33
+ subject.instance_variable_set('@verbose',true)
34
+ end
35
+
36
+ it "must return true" do
37
+ expect(subject.verbose?).to be(true)
38
+ end
39
+ end
40
+
41
+ context "when @verbose is false" do
42
+ before do
43
+ subject.instance_variable_set('@verbose',false)
44
+ end
45
+
46
+ it "must return false" do
47
+ expect(subject.verbose?).to be(false)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+ require 'command_kit/options/version'
3
+
4
+ require 'stringio'
5
+
6
+ describe CommandKit::Options::Version do
7
+ module TestOptionsVersion
8
+ class TestCommandWithoutVersion
9
+
10
+ include CommandKit::Options::Version
11
+
12
+ command_name 'test'
13
+
14
+ end
15
+
16
+ class TestCommandWithVersion
17
+
18
+ include CommandKit::Options::Version
19
+
20
+ command_name 'test'
21
+ version '0.1.0'
22
+
23
+ end
24
+ end
25
+
26
+ describe ".included" do
27
+ subject { TestOptionsVersion::TestCommandWithVersion }
28
+
29
+ it "must include CommandKit::Options" do
30
+ expect(subject).to include(CommandKit::Options)
31
+ end
32
+ end
33
+
34
+ describe ".version" do
35
+ context "when no .version has been previously set" do
36
+ subject { TestOptionsVersion::TestCommandWithoutVersion }
37
+
38
+ it "must return nil" do
39
+ expect(subject.version).to be(nil)
40
+ end
41
+ end
42
+
43
+ context "when a .version has been set" do
44
+ subject { TestOptionsVersion::TestCommandWithVersion }
45
+
46
+ it "must return the set version" do
47
+ expect(subject.version).to eq('0.1.0')
48
+ end
49
+ end
50
+
51
+ context "when the command class inherites from another class" do
52
+ context "but no version are defined" do
53
+ module TestOptionsVersion
54
+ class InheritedCommandWithoutVersion < TestCommandWithoutVersion
55
+ end
56
+ end
57
+
58
+ subject { TestOptionsVersion::InheritedCommandWithoutVersion }
59
+
60
+ it "must return nil" do
61
+ expect(subject.version).to be(nil)
62
+ end
63
+ end
64
+
65
+ context "when the superclass defines the version" do
66
+ module TestOptionsVersion
67
+ class InheritedCommandWithInheritedVersion < TestCommandWithVersion
68
+ end
69
+ end
70
+
71
+ let(:super_class) { TestOptionsVersion::TestCommandWithVersion }
72
+ subject { TestOptionsVersion::InheritedCommandWithInheritedVersion }
73
+
74
+ it "must return the version defined in the superclass" do
75
+ expect(subject.version).to eq(super_class.version)
76
+ end
77
+ end
78
+
79
+ context "when the subclass defines the version" do
80
+ module TestOptionsVersion
81
+ class InheritedCommandWithOwnVersion < TestCommandWithoutVersion
82
+
83
+ version '0.1.0'
84
+
85
+ end
86
+ end
87
+
88
+ let(:super_subject) { TestOptionsVersion::TestCommandWithoutVersion }
89
+ subject { TestOptionsVersion::InheritedCommandWithOwnVersion }
90
+
91
+ it "must return the version set in the subclass" do
92
+ expect(subject.version).to eq('0.1.0')
93
+ end
94
+
95
+ it "must not change the superclass'es version" do
96
+ expect(super_subject.version).to be(nil)
97
+ end
98
+ end
99
+
100
+ context "when subclass overrides the superclass's version" do
101
+ module TestOptionsVersion
102
+ class InheritedCommandThatOverridesVersion < TestCommandWithVersion
103
+
104
+ version '0.2.0'
105
+
106
+ end
107
+ end
108
+
109
+ let(:super_subject) { TestOptionsVersion::TestCommandWithVersion }
110
+ subject { TestOptionsVersion::InheritedCommandThatOverridesVersion }
111
+
112
+ it "must return the version set in the subclass" do
113
+ expect(subject.version).to eq('0.2.0')
114
+ end
115
+
116
+ it "must not change the superclass'es version" do
117
+ expect(super_subject.version).to eq('0.1.0')
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ let(:command_class) { TestOptionsVersion::TestCommandWithVersion }
124
+
125
+ subject { command_class.new }
126
+
127
+ describe "#version" do
128
+ it "must return the class'es .version value" do
129
+ expect(subject.version).to eq(command_class.version)
130
+ end
131
+ end
132
+
133
+ describe "#print_version" do
134
+ let(:stdout) { StringIO.new }
135
+
136
+ subject { command_class.new(stdout: stdout) }
137
+
138
+ it "must print the #command_name and #version" do
139
+ subject.print_version
140
+
141
+ expect(stdout.string).to eq(
142
+ "#{subject.command_name} #{subject.version}#{$/}"
143
+ )
144
+ end
145
+ end
146
+ end
data/spec/options_spec.rb CHANGED
@@ -415,5 +415,51 @@ describe CommandKit::Options do
415
415
  ].join($/)
416
416
  ).to_stdout
417
417
  end
418
+
419
+ context "but when the options are have categories" do
420
+ module TestOptions
421
+ class TestCommandWithOptionsAndCategories
422
+
423
+ include CommandKit::Options
424
+
425
+ option :opt1, short: '-a',
426
+ desc: "Option 1"
427
+ option :opt2, short: '-b',
428
+ desc: "Option 2"
429
+
430
+ option :opt3, short: '-c',
431
+ desc: "Option 3",
432
+ category: 'Other Options'
433
+ option :opt4, short: '-d',
434
+ desc: "Option 4",
435
+ category: 'Other Options'
436
+ end
437
+ end
438
+
439
+ let(:command_class) { TestOptions::TestCommandWithOptionsAndCategories }
440
+
441
+ let(:option1) { command_class.options[:opt1] }
442
+ let(:option2) { command_class.options[:opt2] }
443
+ let(:option3) { command_class.options[:opt3] }
444
+ let(:option4) { command_class.options[:opt4] }
445
+
446
+ it "must group the options by category" do
447
+ expect { subject.help }.to output(
448
+ [
449
+ "Usage: #{subject.usage}",
450
+ '',
451
+ 'Other Options:',
452
+ " #{option3.usage.join(', ').ljust(33 - 1)} #{option3.desc}",
453
+ " #{option4.usage.join(', ').ljust(33 - 1)} #{option4.desc}",
454
+ '',
455
+ 'Options:',
456
+ " #{option1.usage.join(', ').ljust(33 - 1)} #{option1.desc}",
457
+ " #{option2.usage.join(', ').ljust(33 - 1)} #{option2.desc}",
458
+ ' -h, --help Print help information',
459
+ ''
460
+ ].join($/)
461
+ ).to_stdout
462
+ end
463
+ end
418
464
  end
419
465
  end
data/spec/pager_spec.rb CHANGED
@@ -26,7 +26,7 @@ describe CommandKit::Pager do
26
26
 
27
27
  context "when the PAGER env variable is not set" do
28
28
  context "but the PATH env variable is" do
29
- subject { command_class.new(env: {'PATH' => ENV['PATH']}) }
29
+ subject { command_class.new(env: {'PATH' => ENV.fetch('PATH')}) }
30
30
 
31
31
  it "must search PATH for one of the pagers" do
32
32
  expect(subject.instance_variable_get('@pager_command')).to eq("less -r")