hammer_cli 0.19.1 → 2.2.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/bin/hammer-complete +28 -0
  3. data/config/cli_config.template.yml +2 -0
  4. data/config/hammer.completion +5 -0
  5. data/doc/commands_extension.md +12 -0
  6. data/doc/creating_commands.md +100 -0
  7. data/doc/installation.md +47 -4
  8. data/doc/installation_rpm.md +2 -2
  9. data/doc/release_notes.md +31 -6
  10. data/lib/hammer_cli.rb +1 -0
  11. data/lib/hammer_cli/abstract.rb +61 -4
  12. data/lib/hammer_cli/apipie/api_connection.rb +5 -1
  13. data/lib/hammer_cli/apipie/command.rb +3 -2
  14. data/lib/hammer_cli/apipie/option_builder.rb +15 -13
  15. data/lib/hammer_cli/apipie/option_definition.rb +9 -7
  16. data/lib/hammer_cli/bash.rb +2 -0
  17. data/lib/hammer_cli/bash/completion.rb +159 -0
  18. data/lib/hammer_cli/bash/prebuild_command.rb +21 -0
  19. data/lib/hammer_cli/command_extensions.rb +21 -1
  20. data/lib/hammer_cli/connection.rb +4 -0
  21. data/lib/hammer_cli/exception_handler.rb +11 -2
  22. data/lib/hammer_cli/full_help.rb +8 -1
  23. data/lib/hammer_cli/help/builder.rb +29 -3
  24. data/lib/hammer_cli/logger_watch.rb +1 -1
  25. data/lib/hammer_cli/main.rb +5 -3
  26. data/lib/hammer_cli/options/normalizers.rb +7 -3
  27. data/lib/hammer_cli/options/option_definition.rb +26 -6
  28. data/lib/hammer_cli/options/option_family.rb +114 -0
  29. data/lib/hammer_cli/options/predefined.rb +1 -1
  30. data/lib/hammer_cli/output/adapter/abstract.rb +1 -5
  31. data/lib/hammer_cli/output/adapter/base.rb +1 -1
  32. data/lib/hammer_cli/output/adapter/csv.rb +3 -2
  33. data/lib/hammer_cli/output/adapter/json.rb +14 -3
  34. data/lib/hammer_cli/output/adapter/silent.rb +1 -1
  35. data/lib/hammer_cli/output/adapter/table.rb +27 -8
  36. data/lib/hammer_cli/output/adapter/yaml.rb +6 -3
  37. data/lib/hammer_cli/output/output.rb +2 -4
  38. data/lib/hammer_cli/settings.rb +2 -1
  39. data/lib/hammer_cli/subcommand.rb +25 -1
  40. data/lib/hammer_cli/testing/command_assertions.rb +2 -2
  41. data/lib/hammer_cli/utils.rb +22 -0
  42. data/lib/hammer_cli/version.rb +1 -1
  43. data/locale/ca/LC_MESSAGES/hammer-cli.mo +0 -0
  44. data/locale/de/LC_MESSAGES/hammer-cli.mo +0 -0
  45. data/locale/en/LC_MESSAGES/hammer-cli.mo +0 -0
  46. data/locale/en_GB/LC_MESSAGES/hammer-cli.mo +0 -0
  47. data/locale/es/LC_MESSAGES/hammer-cli.mo +0 -0
  48. data/locale/fr/LC_MESSAGES/hammer-cli.mo +0 -0
  49. data/locale/it/LC_MESSAGES/hammer-cli.mo +0 -0
  50. data/locale/ja/LC_MESSAGES/hammer-cli.mo +0 -0
  51. data/locale/ko/LC_MESSAGES/hammer-cli.mo +0 -0
  52. data/locale/pt_BR/LC_MESSAGES/hammer-cli.mo +0 -0
  53. data/locale/ru/LC_MESSAGES/hammer-cli.mo +0 -0
  54. data/locale/zh_CN/LC_MESSAGES/hammer-cli.mo +0 -0
  55. data/locale/zh_TW/LC_MESSAGES/hammer-cli.mo +0 -0
  56. data/test/unit/abstract_test.rb +23 -2
  57. data/test/unit/apipie/api_connection_test.rb +1 -0
  58. data/test/unit/apipie/option_builder_test.rb +8 -0
  59. data/test/unit/bash_test.rb +138 -0
  60. data/test/unit/command_extensions_test.rb +67 -49
  61. data/test/unit/exception_handler_test.rb +44 -0
  62. data/test/unit/help/builder_test.rb +22 -0
  63. data/test/unit/options/option_family_test.rb +48 -0
  64. data/test/unit/output/adapter/base_test.rb +58 -0
  65. data/test/unit/output/adapter/csv_test.rb +63 -1
  66. data/test/unit/output/adapter/json_test.rb +61 -0
  67. data/test/unit/output/adapter/table_test.rb +70 -1
  68. data/test/unit/output/adapter/yaml_test.rb +59 -0
  69. data/test/unit/output/output_test.rb +3 -3
  70. metadata +17 -6
  71. data/hammer_cli_complete +0 -13
@@ -55,4 +55,48 @@ describe HammerCLI::ExceptionHandler do
55
55
  assert_match /ERROR Exception : (Resource )?Not Found/, @log_output.readline.strip
56
56
  end
57
57
 
58
+ it "should print default prompts for standard missing arguments" do
59
+ params = %w[login mail]
60
+ heading = 'Could not create user:'
61
+ body = "Missing arguments for '--login', '--mail'."
62
+ ex = ApipieBindings::MissingArgumentsError.new(params)
63
+ output.expects(:print_error).with(heading, body)
64
+ handler.handle_exception(ex, heading: heading)
65
+ end
66
+
67
+ it "should print right prompts for nested missing arguments" do
68
+ params = %w[user[login] user[mail]]
69
+ heading = 'Could not create user:'
70
+ body = "Missing arguments for '--login', '--mail'."
71
+ ex = ApipieBindings::MissingArgumentsError.new(params)
72
+ output.expects(:print_error).with(heading, body)
73
+ handler.handle_exception(ex, heading: heading)
74
+ end
75
+
76
+ it "should print simple prompts for even more nested arguments" do
77
+ params = %w[user[address][city] user[address][street]]
78
+ heading = 'Could not create user:'
79
+ body = "Missing arguments for '--address'."
80
+ ex = ApipieBindings::MissingArgumentsError.new(params)
81
+ output.expects(:print_error).with(heading, body)
82
+ handler.handle_exception(ex, heading: heading)
83
+ end
84
+
85
+ it "should print simple prompts for even more different nested arguments" do
86
+ params = %w[user[address][city] user[address][street] user[nested][par1]]
87
+ heading = 'Could not create user:'
88
+ body = "Missing arguments for '--address', '--nested'."
89
+ ex = ApipieBindings::MissingArgumentsError.new(params)
90
+ output.expects(:print_error).with(heading, body)
91
+ handler.handle_exception(ex, heading: heading)
92
+ end
93
+
94
+ it "should print default prompts for standard missing arguments" do
95
+ params = %w[opt_abc opt_a_b-c]
96
+ heading = 'Could not create user:'
97
+ body = "Missing arguments for '--opt-abc', '--opt-a-b-c'."
98
+ ex = ApipieBindings::MissingArgumentsError.new(params)
99
+ output.expects(:print_error).with(heading, body)
100
+ handler.handle_exception(ex, heading: heading)
101
+ end
58
102
  end
@@ -69,4 +69,26 @@ describe HammerCLI::Help::Builder do
69
69
  help.string.strip.must_equal expected_output
70
70
  end
71
71
  end
72
+
73
+ describe 'option family' do
74
+ let(:family) { Class.new(HammerCLI::Options::OptionFamily) }
75
+
76
+ it 'prints option families' do
77
+ fm1 = family.new
78
+ fm1.parent(['--option-zzz'], 'OPT', 'Some description')
79
+ fm1.child(['--option-aaa'], 'OPT', 'Some description')
80
+ fm2 = family.new
81
+ fm2.parent(['--option-bbb'], 'OPT', 'Some description')
82
+ fm2.child(['--option-yyy'], 'OPT', 'Some description')
83
+
84
+ options = fm1.all + fm2.all
85
+ help.add_list('Options', options)
86
+
87
+ help.string.strip.must_equal [
88
+ 'Options:',
89
+ ' --option[-yyy|-bbb] Some description',
90
+ ' --option[-aaa|-zzz] Some description',
91
+ ].join("\n")
92
+ end
93
+ end
72
94
  end
@@ -0,0 +1,48 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe HammerCLI::Options::OptionFamily do
4
+ let(:family) do
5
+ HammerCLI::Options::OptionFamily.new(
6
+ deprecated: { '--test-one' => 'Use --test-two instead' }
7
+ )
8
+ end
9
+ let(:first_option) { HammerCLI::Apipie::OptionDefinition.new("--test-one", '', '') }
10
+ let(:second_option) { HammerCLI::Apipie::OptionDefinition.new("--test-two", '', '') }
11
+ let(:third_option) { HammerCLI::Apipie::OptionDefinition.new("--test-three", '', '') }
12
+ let(:full_family) do
13
+ family.parent('--test-one', '', 'Test').family.child('--test-two', '', '').family
14
+ end
15
+
16
+ describe 'switch' do
17
+ it 'returns nil if family is empty' do
18
+ family.switch.must_be_nil
19
+ end
20
+
21
+ it 'returns parent switch if family has no children' do
22
+ family.parent('--test-one', '', '')
23
+ family.switch.must_equal '--test-one'
24
+ end
25
+
26
+ it 'returns switch based on members' do
27
+ full_family.switch.must_equal '--test[-two|-one]'
28
+ end
29
+ end
30
+
31
+ describe 'description' do
32
+ it 'returns parent description if nothing passed to initializer' do
33
+ full_family.description.must_equal full_family.head.help[1]
34
+ end
35
+
36
+ it 'returns description with deprecation message' do
37
+ full_family.description.must_equal 'Test (--test-one is deprecated: Use --test-two instead)'
38
+ end
39
+ end
40
+
41
+ describe 'adopt' do
42
+ it 'appends an option to children' do
43
+ full_family.adopt(third_option)
44
+ full_family.children.size.must_equal 2
45
+ third_option.family.must_equal full_family
46
+ end
47
+ end
48
+ end
@@ -199,6 +199,64 @@ describe HammerCLI::Output::Adapter::Base do
199
199
  proc { adapter.print_collection(fields, data) }.must_output(expected_output)
200
200
  end
201
201
 
202
+ context 'printing by chunks' do
203
+ let(:context) { { show_ids: true } }
204
+ let(:collection_count) { 30 }
205
+ let(:collection_data) do
206
+ collection = collection_count.times.each_with_object([]) do |t, r|
207
+ r << { id: t, name: "John #{t}"}
208
+ end
209
+ HammerCLI::Output::RecordCollection.new(collection)
210
+ end
211
+ let(:fields) { [id, name] }
212
+
213
+ it 'prints single chunk' do
214
+ expected_output = collection_count.times.each_with_object([]) do |t, r|
215
+ r << ["Id: #{t}", "Name: John #{t}", "\n"].join("\n")
216
+ end.flatten(1).join
217
+
218
+ proc do
219
+ adapter.print_collection(fields, collection_data)
220
+ end.must_output(expected_output)
221
+ end
222
+
223
+ it 'prints first chunk' do
224
+ expected_output = 10.times.each_with_object([]) do |t, r|
225
+ r << ["Id: #{t}", "Name: John #{t}", "\n"].join("\n")
226
+ end.flatten(1).join
227
+
228
+ proc do
229
+ adapter.print_collection(
230
+ fields, collection_data[0...10], current_chunk: :first
231
+ )
232
+ end.must_output(expected_output)
233
+ end
234
+
235
+ it 'prints another chunk' do
236
+ expected_output = (10...20).each_with_object([]) do |t, r|
237
+ r << ["Id: #{t}", "Name: John #{t}", "\n"].join("\n")
238
+ end.flatten(1).join
239
+
240
+ proc do
241
+ adapter.print_collection(
242
+ fields, collection_data[10...20], current_chunk: :another
243
+ )
244
+ end.must_output(expected_output)
245
+ end
246
+
247
+ it 'prints last chunk' do
248
+ expected_output = (20...30).each_with_object([]) do |t, r|
249
+ r << ["Id: #{t}", "Name: John #{t}", "\n"].join("\n")
250
+ end.flatten(1).join
251
+
252
+ proc do
253
+ adapter.print_collection(
254
+ fields, collection_data[20...30], current_chunk: :last
255
+ )
256
+ end.must_output(expected_output)
257
+ end
258
+ end
259
+
202
260
  context "show ids" do
203
261
 
204
262
  let(:context) { {:show_ids => true} }
@@ -9,7 +9,7 @@ describe HammerCLI::Output::Adapter::CSValues do
9
9
  end
10
10
 
11
11
  context "print_collection" do
12
-
12
+ let(:field_id) { Fields::Id.new(:path => [:id], :label => "Id") }
13
13
  let(:field_name) { Fields::Field.new(:path => [:name], :label => "Name") }
14
14
  let(:field_started_at) { Fields::Field.new(:path => [:started_at], :label => "Started At") }
15
15
  let(:field_login) { Fields::Field.new(:path => [:login], :label => "Login") }
@@ -220,6 +220,68 @@ describe HammerCLI::Output::Adapter::CSValues do
220
220
  end
221
221
  end
222
222
 
223
+ context 'printing by chunks' do
224
+ let(:adapter) { HammerCLI::Output::Adapter::CSValues.new(show_ids: true) }
225
+ let(:collection_count) { 30 }
226
+ let(:collection_data) do
227
+ collection = collection_count.times.each_with_object([]) do |t, r|
228
+ r << { id: t, name: "John #{t}"}
229
+ end
230
+ HammerCLI::Output::RecordCollection.new(collection)
231
+ end
232
+ let(:fields) { [field_id, field_name] }
233
+
234
+ it 'prints single chunk' do
235
+ expected_output = collection_count.times.each_with_object([]) do |t, r|
236
+ r << ["#{t}", "John #{t}"].join(',')
237
+ end.flatten(1).unshift('Id,Name').join("\n") + "\n"
238
+
239
+ out, _err = capture_io do
240
+ adapter.print_collection(fields, collection_data)
241
+ end
242
+ out.must_equal(expected_output)
243
+ end
244
+
245
+ it 'prints first chunk' do
246
+ expected_output = 10.times.each_with_object([]) do |t, r|
247
+ r << ["#{t}", "John #{t}"].join(',')
248
+ end.flatten(1).unshift('Id,Name').join("\n") + "\n"
249
+
250
+ out, _err = capture_io do
251
+ adapter.print_collection(
252
+ fields, collection_data[0...10], current_chunk: :first
253
+ )
254
+ end
255
+ out.must_equal(expected_output)
256
+ end
257
+
258
+ it 'prints another chunk' do
259
+ expected_output = (10...20).each_with_object([]) do |t, r|
260
+ r << ["#{t}", "John #{t}"].join(',')
261
+ end.flatten(1).join("\n") + "\n"
262
+
263
+ out, _err = capture_io do
264
+ adapter.print_collection(
265
+ fields, collection_data[10...20], current_chunk: :another
266
+ )
267
+ end
268
+ out.must_equal(expected_output)
269
+ end
270
+
271
+ it 'prints last chunk' do
272
+ expected_output = (20...30).each_with_object([]) do |t, r|
273
+ r << ["#{t}", "John #{t}"].join(',')
274
+ end.flatten(1).join("\n") + "\n"
275
+
276
+ out, _err = capture_io do
277
+ adapter.print_collection(
278
+ fields, collection_data[20...30], current_chunk: :last
279
+ )
280
+ end
281
+ out.must_equal(expected_output)
282
+ end
283
+ end
284
+
223
285
  context "output_stream" do
224
286
 
225
287
  let(:tempfile) { Tempfile.new("output_stream_csv_test_temp") }
@@ -316,6 +316,67 @@ describe HammerCLI::Output::Adapter::Json do
316
316
  end
317
317
  end
318
318
 
319
+ context 'printing by chunks' do
320
+ let(:settings) { HammerCLI::Settings }
321
+ let(:context) { { show_ids: true, capitalization: HammerCLI.capitalization } }
322
+ let(:collection_count) { 30 }
323
+ let(:collection) do
324
+ collection_count.times.each_with_object([]) do |t, r|
325
+ r << { id: t, name: "John #{t}"}
326
+ end
327
+ end
328
+ let(:collection_data) do
329
+ HammerCLI::Output::RecordCollection.new(collection)
330
+ end
331
+ let(:fields) { [id, name] }
332
+
333
+ before do
334
+ settings.load(ui: { capitalization: :downcase })
335
+ end
336
+
337
+ it 'prints single chunk' do
338
+ expected_output = JSON.pretty_generate(collection) + "\n"
339
+
340
+ out, _err = capture_io do
341
+ adapter.print_collection(fields, collection_data)
342
+ end
343
+ out.must_equal(expected_output)
344
+ end
345
+
346
+ it 'prints first chunk' do
347
+ expected_output = JSON.pretty_generate(collection[0...10])[0...-2] + ",\n"
348
+
349
+ out, _err = capture_io do
350
+ adapter.print_collection(
351
+ fields, collection_data[0...10], current_chunk: :first
352
+ )
353
+ end
354
+ out.must_equal(expected_output)
355
+ end
356
+
357
+ it 'prints another chunk' do
358
+ expected_output = JSON.pretty_generate(collection[10...20])[2...-2] + ",\n"
359
+
360
+ out, _err = capture_io do
361
+ adapter.print_collection(
362
+ fields, collection_data[10...20], current_chunk: :another
363
+ )
364
+ end
365
+ out.must_equal(expected_output)
366
+ end
367
+
368
+ it 'prints last chunk' do
369
+ expected_output = JSON.pretty_generate(collection[20...30])[2..-1] + "\n"
370
+
371
+ out, _err = capture_io do
372
+ adapter.print_collection(
373
+ fields, collection_data[20...30], current_chunk: :last
374
+ )
375
+ end
376
+ out.must_equal(expected_output)
377
+ end
378
+ end
379
+
319
380
  context "show ids" do
320
381
 
321
382
  let(:context) { {:show_ids => true} }
@@ -9,7 +9,7 @@ describe HammerCLI::Output::Adapter::Table do
9
9
  end
10
10
 
11
11
  context "print_collection" do
12
-
12
+ let(:field_id) { Fields::Id.new(:path => [:id], :label => "Id") }
13
13
  let(:field_name) { Fields::Field.new(:path => [:fullname], :label => "Name") }
14
14
  let(:field_firstname) { Fields::Field.new(:path => [:firstname], :label => "Firstname") }
15
15
  let(:field_lastname) { Fields::Field.new(:path => [:lastname], :label => "Lastname") }
@@ -380,6 +380,75 @@ describe HammerCLI::Output::Adapter::Table do
380
380
 
381
381
  end
382
382
 
383
+ context 'printing by chunks' do
384
+ let(:adapter) { HammerCLI::Output::Adapter::Table.new(show_ids: true) }
385
+ let(:collection_count) { 30 }
386
+ let(:collection_data) do
387
+ collection = collection_count.times.each_with_object([]) do |t, r|
388
+ r << { id: t, fullname: "John Doe #{t}"}
389
+ end
390
+ HammerCLI::Output::RecordCollection.new(collection)
391
+ end
392
+ let(:fields) { [field_id, field_name] }
393
+
394
+ it 'prints single chunk' do
395
+ expected_output = collection_count.times.each_with_object([]) do |t, r|
396
+ sp = t < 10 ? ' ' : ''
397
+ r << ["#{t} #{sp}| John Doe #{t}#{sp}"]
398
+ end.flatten(1).unshift(
399
+ '---|------------',
400
+ 'ID | NAME ',
401
+ "---|------------",
402
+ ).join("\n") + "\n---|------------\n"
403
+
404
+ proc do
405
+ adapter.print_collection(fields, collection_data)
406
+ end.must_output(expected_output)
407
+ end
408
+
409
+ it 'prints first chunk' do
410
+ expected_output = (0...10).each_with_object([]) do |t, r|
411
+ r << [
412
+ "#{t} | John Doe #{t}"
413
+ ]
414
+ end.flatten(1).unshift(
415
+ '---|-----------',
416
+ 'ID | NAME ',
417
+ "---|-----------",
418
+ ).join("\n") + "\n"
419
+
420
+ proc do
421
+ adapter.print_collection(
422
+ fields, collection_data[0...10], current_chunk: :first
423
+ )
424
+ end.must_output(expected_output)
425
+ end
426
+
427
+ it 'prints another chunk' do
428
+ expected_output = (10...20).each_with_object([]) do |t, r|
429
+ r << ["#{t} | John Doe #{t}"]
430
+ end.flatten(1).join("\n") + "\n"
431
+
432
+ proc do
433
+ adapter.print_collection(
434
+ fields, collection_data[10...20], current_chunk: :another
435
+ )
436
+ end.must_output(expected_output)
437
+ end
438
+ #
439
+ it 'prints last chunk' do
440
+ expected_output = (20...30).each_with_object([]) do |t, r|
441
+ r << ["#{t} | John Doe #{t}"]
442
+ end.flatten(1).join("\n") + "\n---|------------\n"
443
+
444
+ proc do
445
+ adapter.print_collection(
446
+ fields, collection_data[20...30], current_chunk: :last
447
+ )
448
+ end.must_output(expected_output)
449
+ end
450
+ end
451
+
383
452
  context "output_stream" do
384
453
 
385
454
  let(:tempfile) { Tempfile.new("output_stream_table_test_temp") }
@@ -312,6 +312,65 @@ describe HammerCLI::Output::Adapter::Yaml do
312
312
  end
313
313
  end
314
314
 
315
+ context 'printing by chunks' do
316
+ let(:context) { { show_ids: true } }
317
+ let(:collection_count) { 30 }
318
+ let(:collection) do
319
+ collection_count.times.each_with_object([]) do |t, r|
320
+ r << { id: t, name: "John #{t}"}
321
+ end
322
+ end
323
+ let(:prepared_collection) do
324
+ collection.map { |i| i.transform_keys { |k| k.to_s.capitalize } }
325
+ end
326
+ let(:collection_data) do
327
+ HammerCLI::Output::RecordCollection.new(collection)
328
+ end
329
+ let(:fields) { [id, name] }
330
+
331
+ it 'prints single chunk' do
332
+ expected_output = YAML.dump(prepared_collection)
333
+
334
+ out, _err = capture_io do
335
+ adapter.print_collection(fields, collection_data)
336
+ end
337
+ out.must_equal(expected_output)
338
+ end
339
+
340
+ it 'prints first chunk' do
341
+ expected_output = YAML.dump(prepared_collection[0...10])
342
+
343
+ out, _err = capture_io do
344
+ adapter.print_collection(
345
+ fields, collection_data[0...10], current_chunk: :first
346
+ )
347
+ end
348
+ out.must_equal(expected_output)
349
+ end
350
+
351
+ it 'prints another chunk' do
352
+ expected_output = YAML.dump(prepared_collection[10...20])[4..-1]
353
+
354
+ out, _err = capture_io do
355
+ adapter.print_collection(
356
+ fields, collection_data[10...20], current_chunk: :another
357
+ )
358
+ end
359
+ out.must_equal(expected_output)
360
+ end
361
+
362
+ it 'prints last chunk' do
363
+ expected_output = YAML.dump(prepared_collection[20...30])[4..-1]
364
+
365
+ out, _err = capture_io do
366
+ adapter.print_collection(
367
+ fields, collection_data[20...30], current_chunk: :last
368
+ )
369
+ end
370
+ out.must_equal(expected_output)
371
+ end
372
+ end
373
+
315
374
  context "show ids" do
316
375
 
317
376
  let(:context) { {:show_ids => true} }