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
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")