hammer_cli 0.0.18 → 0.1.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -314
  3. data/bin/hammer +45 -6
  4. data/config/cli.modules.d/module_config_template.yml +4 -0
  5. data/config/cli_config.template.yml +9 -11
  6. data/doc/developer_docs.md +1 -0
  7. data/doc/i18n.md +85 -0
  8. data/doc/installation.md +321 -0
  9. data/lib/hammer_cli.rb +3 -0
  10. data/lib/hammer_cli/abstract.rb +15 -24
  11. data/lib/hammer_cli/apipie/command.rb +13 -7
  12. data/lib/hammer_cli/apipie/options.rb +14 -16
  13. data/lib/hammer_cli/apipie/read_command.rb +6 -1
  14. data/lib/hammer_cli/apipie/resource.rb +48 -58
  15. data/lib/hammer_cli/apipie/write_command.rb +5 -1
  16. data/lib/hammer_cli/completer.rb +77 -21
  17. data/lib/hammer_cli/connection.rb +44 -0
  18. data/lib/hammer_cli/exception_handler.rb +15 -4
  19. data/lib/hammer_cli/exceptions.rb +6 -0
  20. data/lib/hammer_cli/i18n.rb +95 -0
  21. data/lib/hammer_cli/logger.rb +3 -3
  22. data/lib/hammer_cli/main.rb +12 -11
  23. data/lib/hammer_cli/modules.rb +19 -6
  24. data/lib/hammer_cli/options/normalizers.rb +42 -7
  25. data/lib/hammer_cli/options/option_definition.rb +2 -2
  26. data/lib/hammer_cli/output.rb +1 -0
  27. data/lib/hammer_cli/output/adapter/abstract.rb +20 -0
  28. data/lib/hammer_cli/output/adapter/base.rb +49 -78
  29. data/lib/hammer_cli/output/adapter/csv.rb +5 -5
  30. data/lib/hammer_cli/output/adapter/table.rb +41 -10
  31. data/lib/hammer_cli/output/dsl.rb +1 -1
  32. data/lib/hammer_cli/output/field_filter.rb +21 -0
  33. data/lib/hammer_cli/output/fields.rb +44 -78
  34. data/lib/hammer_cli/output/formatters.rb +38 -0
  35. data/lib/hammer_cli/settings.rb +28 -6
  36. data/lib/hammer_cli/shell.rb +58 -57
  37. data/lib/hammer_cli/utils.rb +14 -0
  38. data/lib/hammer_cli/validator.rb +5 -5
  39. data/lib/hammer_cli/version.rb +1 -1
  40. data/locale/Makefile +64 -0
  41. data/locale/hammer-cli.pot +203 -0
  42. data/locale/zanata.xml +29 -0
  43. data/test/unit/apipie/command_test.rb +42 -25
  44. data/test/unit/apipie/read_command_test.rb +10 -7
  45. data/test/unit/apipie/write_command_test.rb +9 -8
  46. data/test/unit/completer_test.rb +206 -21
  47. data/test/unit/connection_test.rb +68 -0
  48. data/test/unit/fixtures/apipie/architectures.json +153 -0
  49. data/test/unit/fixtures/apipie/documented.json +79 -0
  50. data/test/unit/fixtures/json_input/invalid.json +12 -0
  51. data/test/unit/fixtures/json_input/valid.json +12 -0
  52. data/test/unit/history_test.rb +71 -0
  53. data/test/unit/main_test.rb +9 -0
  54. data/test/unit/modules_test.rb +22 -6
  55. data/test/unit/options/field_filter_test.rb +27 -0
  56. data/test/unit/options/normalizers_test.rb +53 -0
  57. data/test/unit/output/adapter/base_test.rb +162 -10
  58. data/test/unit/output/adapter/csv_test.rb +16 -3
  59. data/test/unit/output/adapter/table_test.rb +97 -13
  60. data/test/unit/output/dsl_test.rb +74 -6
  61. data/test/unit/output/fields_test.rb +93 -62
  62. data/test/unit/output/formatters_test.rb +47 -0
  63. data/test/unit/settings_test.rb +35 -4
  64. data/test/unit/utils_test.rb +45 -0
  65. metadata +85 -4
  66. data/test/unit/apipie/fake_api.rb +0 -101
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+
3
+ describe HammerCLI::Output::FieldFilter do
4
+
5
+ let(:fields) { [
6
+ Fields::Field.new(:label => "field"),
7
+ Fields::Collection.new(:label => "collection"),
8
+ Fields::Id.new(:label => "id")
9
+ ] }
10
+ let(:field_labels) { fields.map(&:label).sort }
11
+
12
+ it "lets all fields go by default" do
13
+ f = HammerCLI::Output::FieldFilter.new
14
+ f.filter(fields).map(&:label).sort.must_equal ["field", "collection", "id"].sort
15
+ end
16
+
17
+ it "filters fields by class" do
18
+ f = HammerCLI::Output::FieldFilter.new([Fields::Id])
19
+ f.filter(fields).map(&:label).sort.must_equal ["field", "collection"].sort
20
+ end
21
+
22
+ it "filters fields by superclass" do
23
+ f = HammerCLI::Output::FieldFilter.new([Fields::ContainerField])
24
+ f.filter(fields).map(&:label).sort.must_equal ["field", "id"].sort
25
+ end
26
+
27
+ end
@@ -122,6 +122,33 @@ describe HammerCLI::Options::Normalizers do
122
122
  end
123
123
  end
124
124
 
125
+ describe 'json input' do
126
+ let(:formatter) { HammerCLI::Options::Normalizers::JSONInput.new }
127
+
128
+ it "should return a hash on valid json file" do
129
+ file = File.join(File.dirname(__FILE__), '../fixtures/json_input/valid.json')
130
+ formatter.format(file).must_equal({ "units" => [ { "name" => "zip", "version" => "10.0" },
131
+ { "name" => "zap", "version" => "9.0" }] })
132
+ end
133
+
134
+ it "should raise exception on invalid json file contents" do
135
+ file = File.join(File.dirname(__FILE__), '../fixtures/json_input/invalid.json')
136
+ proc { formatter.format(file) }.must_raise ArgumentError
137
+ end
138
+
139
+ it "should return a hash on valid json string" do
140
+ json_string = '{ "units":[{ "name":"zip", "version":"10.0" }, { "name":"zap", "version":"9.0" }] }'
141
+ formatter.format(json_string).must_equal({ "units" => [ { "name" => "zip", "version" => "10.0" },
142
+ { "name" => "zap", "version" => "9.0" }] })
143
+ end
144
+
145
+ it "should raise exception on invalid json string" do
146
+ json_string = "{ units:[{ name:zip, version:10.0 }, { name:zap, version:9.0 }] }"
147
+ proc { formatter.format(json_string) }.must_raise ArgumentError
148
+ end
149
+
150
+ end
151
+
125
152
  describe 'enum' do
126
153
 
127
154
  let(:formatter) { HammerCLI::Options::Normalizers::Enum.new ['a', 'b'] }
@@ -144,5 +171,31 @@ describe HammerCLI::Options::Normalizers do
144
171
 
145
172
  end
146
173
 
174
+ describe 'datetime' do
175
+
176
+ let(:formatter) { HammerCLI::Options::Normalizers::DateTime.new }
177
+
178
+ it "should raise argument error when the value is nil" do
179
+ proc { formatter.format(nil) }.must_raise ArgumentError
180
+ end
181
+
182
+ it "should raise argument error when the value is not a date" do
183
+ proc { formatter.format("not a date") }.must_raise ArgumentError
184
+ end
185
+
186
+ it "should accept and parse iso8601" do
187
+ formatter.format("1986-01-01T08:30:20").must_equal("1986-01-01T08:30:20+00:00")
188
+ end
189
+
190
+ it "should accept and parse YYYY-MM-DD HH:MM:SS" do
191
+ formatter.format("1986-01-01 08:30:20").must_equal("1986-01-01T08:30:20+00:00")
192
+ end
193
+
194
+ it "should accept and parse YYYY/MM/DD HH:MM:SS" do
195
+ formatter.format("1986/01/01 08:30:20").must_equal("1986-01-01T08:30:20+00:00")
196
+ end
197
+
198
+ end
199
+
147
200
  end
148
201
 
@@ -2,24 +2,176 @@ require File.join(File.dirname(__FILE__), '../../test_helper')
2
2
 
3
3
  describe HammerCLI::Output::Adapter::Base do
4
4
 
5
- let(:adapter) { HammerCLI::Output::Adapter::Base.new }
5
+ let(:context) {{}}
6
+ let(:adapter) { HammerCLI::Output::Adapter::Base.new(context, HammerCLI::Output::Output.formatters) }
6
7
 
7
8
  context "print_collection" do
8
9
 
9
- let(:field_name) { Fields::DataField.new(:path => [:name], :label => "Name") }
10
- let(:fields) {
11
- [field_name]
12
- }
10
+ let(:id) { Fields::Id.new(:path => [:id], :label => "Id") }
11
+ let(:name) { Fields::Field.new(:path => [:name], :label => "Name") }
12
+ let(:unlabeled) { Fields::Field.new(:path => [:name]) }
13
+ let(:surname) { Fields::Field.new(:path => [:surname], :label => "Surname") }
14
+ let(:address_city) { Fields::Field.new(:path => [:address, :city], :label => "City") }
15
+ let(:city) { Fields::Field.new(:path => [:city], :label => "City") }
16
+ let(:label_address) { Fields::Label.new(:path => [:address], :label => "Address") }
17
+ let(:contacts) { Fields::Collection.new(:path => [:contacts], :label => "Contacts") }
18
+ let(:desc) { Fields::Field.new(:path => [:desc], :label => "Description") }
19
+ let(:contact) { Fields::Field.new(:path => [:contact], :label => "Contact") }
20
+ let(:params) { Fields::KeyValueList.new(:path => [:params], :label => "Parameters") }
21
+ let(:params_collection) { Fields::Collection.new(:path => [:params], :label => "Parameters") }
22
+ let(:param) { Fields::KeyValue.new(:path => nil, :label => nil) }
23
+ let(:blank) { Fields::Field.new(:path => [:blank], :label => "Blank", :hide_blank => true) }
24
+
13
25
  let(:data) { HammerCLI::Output::RecordCollection.new [{
14
- :name => "John Doe"
26
+ :id => 112,
27
+ :name => "John",
28
+ :surname => "Doe",
29
+ :address => {
30
+ :city => "New York"
31
+ },
32
+ :contacts => [
33
+ {
34
+ :desc => 'personal email',
35
+ :contact => 'john.doe@doughnut.com'
36
+ },
37
+ {
38
+ :desc => 'telephone',
39
+ :contact => '123456789'
40
+ }
41
+ ],
42
+ :params => [
43
+ {
44
+ :name => 'weight',
45
+ :value => '83'
46
+ },
47
+ {
48
+ :name => 'size',
49
+ :value => '32'
50
+ }
51
+ ]
15
52
  }]}
16
53
 
17
- it "should print field name" do
18
- proc { adapter.print_collection(fields, data) }.must_output(/.*Name[ ]*:.*/, "")
54
+ it "should print one field" do
55
+ fields = [name]
56
+ expected_output = [
57
+ "Name: John",
58
+ "\n"
59
+ ].join("\n")
60
+
61
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
62
+ end
63
+
64
+ it "doesn't print label when it's nil" do
65
+ fields = [unlabeled]
66
+ expected_output = [
67
+ "John",
68
+ "\n"
69
+ ].join("\n")
70
+
71
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
72
+ end
73
+
74
+ it "aligns multiple fields" do
75
+ fields = [name, surname, unlabeled]
76
+ expected_output = [
77
+ "Name: John",
78
+ "Surname: Doe",
79
+ "John",
80
+ "\n"
81
+ ].join("\n")
82
+
83
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
84
+ end
85
+
86
+ it "should field with nested data" do
87
+ fields = [address_city]
88
+ expected_output = [
89
+ "City: New York",
90
+ "\n"
91
+ ].join("\n")
92
+
93
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
94
+ end
95
+
96
+ it "should print labeled fields" do
97
+ label_address.output_definition.append [city]
98
+ fields = [label_address]
99
+
100
+ expected_output = [
101
+ "Address: ",
102
+ " City: New York",
103
+ "\n"
104
+ ].join("\n")
105
+
106
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
107
+ end
108
+
109
+
110
+ it "should print collection" do
111
+ contacts.output_definition.append [desc, contact]
112
+ fields = [contacts]
113
+
114
+ expected_output = [
115
+ "Contacts: ",
116
+ " 1) Description: personal email",
117
+ " Contact: john.doe@doughnut.com",
118
+ " 2) Description: telephone",
119
+ " Contact: 123456789",
120
+ "\n"
121
+ ].join("\n")
122
+
123
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
124
+ end
125
+
126
+ it "hides ids by default" do
127
+ fields = [id, name]
128
+ expected_output = [
129
+ "Name: John",
130
+ "\n"
131
+ ].join("\n")
132
+
133
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
134
+ end
135
+
136
+ it "skips blank values" do
137
+ fields = [name, blank]
138
+ expected_output = [
139
+ "Name: John",
140
+ "\n"
141
+ ].join("\n")
142
+
143
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
19
144
  end
20
145
 
21
- it "should print field value" do
22
- proc { adapter.print_collection(fields, data) }.must_output(/.*John Doe.*/, "")
146
+ it "should print key -> value" do
147
+ params_collection.output_definition.append [param]
148
+ fields = [params_collection]
149
+
150
+ expected_output = [
151
+ "Parameters: ",
152
+ " 1) weight => 83",
153
+ " 2) size => 32",
154
+ "\n"
155
+ ].join("\n")
156
+
157
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
158
+ end
159
+
160
+ context "show ids" do
161
+
162
+ let(:context) { {:show_ids => true} }
163
+
164
+ it "shows ids if it's required in the context" do
165
+ fields = [id, name]
166
+ expected_output = [
167
+ "Id: 112",
168
+ "Name: John",
169
+ "\n"
170
+ ].join("\n")
171
+
172
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
173
+ end
174
+
23
175
  end
24
176
 
25
177
  end
@@ -6,8 +6,8 @@ describe HammerCLI::Output::Adapter::CSValues do
6
6
 
7
7
  context "print_collection" do
8
8
 
9
- let(:field_name) { Fields::DataField.new(:path => [:name], :label => "Name") }
10
- let(:field_started_at) { Fields::DataField.new(:path => [:started_at], :label => "Started At") }
9
+ let(:field_name) { Fields::Field.new(:path => [:name], :label => "Name") }
10
+ let(:field_started_at) { Fields::Field.new(:path => [:started_at], :label => "Started At") }
11
11
  let(:fields) {
12
12
  [field_name, field_started_at]
13
13
  }
@@ -52,10 +52,23 @@ describe HammerCLI::Output::Adapter::CSValues do
52
52
  end
53
53
  end
54
54
 
55
- adapter = HammerCLI::Output::Adapter::CSValues.new({}, { :DataField => [ DotFormatter.new ]})
55
+ adapter = HammerCLI::Output::Adapter::CSValues.new({}, { :Field => [ DotFormatter.new ]})
56
56
  out, err = capture_io { adapter.print_collection(fields, data) }
57
57
  out.must_match(/.*-DOT-.*/)
58
58
  end
59
+
60
+ it "should not replace nil with empty string before it applies formatters" do
61
+ class NilFormatter < HammerCLI::Output::Formatters::FieldFormatter
62
+ def format(data)
63
+ 'NIL' if data.nil?
64
+ end
65
+ end
66
+
67
+ adapter = HammerCLI::Output::Adapter::CSValues.new({}, { :Field => [ NilFormatter.new ]})
68
+ nil_data = HammerCLI::Output::RecordCollection.new [{ :name => nil }]
69
+ out, err = capture_io { adapter.print_collection([field_name], nil_data) }
70
+ out.must_match(/.*NIL.*/)
71
+ end
59
72
  end
60
73
  end
61
74
 
@@ -6,12 +6,20 @@ describe HammerCLI::Output::Adapter::Table do
6
6
 
7
7
  context "print_collection" do
8
8
 
9
- let(:field_name) { Fields::DataField.new(:path => [:name], :label => "Name") }
9
+ let(:field_name) { Fields::Field.new(:path => [:fullname], :label => "Name") }
10
+ let(:field_firstname) { Fields::Field.new(:path => [:firstname], :label => "Firstname") }
11
+ let(:field_lastname) { Fields::Field.new(:path => [:lastname], :label => "Lastname") }
12
+
10
13
  let(:fields) {
11
14
  [field_name]
12
15
  }
16
+
13
17
  let(:data) { HammerCLI::Output::RecordCollection.new [{
14
- :name => "John Doe"
18
+ :id => 1,
19
+ :firstname => "John",
20
+ :lastname => "Doe",
21
+ :fullname => "John Doe",
22
+ :long => "SomeVeryLongString"
15
23
  }]}
16
24
 
17
25
  it "should print column name " do
@@ -40,6 +48,74 @@ describe HammerCLI::Output::Adapter::Table do
40
48
  end
41
49
  end
42
50
 
51
+ context "column width" do
52
+
53
+ it "truncates string when it exceeds maximum width" do
54
+ first_field = Fields::Field.new(:path => [:long], :label => "Long", :max_width => 10)
55
+ fields = [first_field, field_lastname]
56
+
57
+ expected_output = [
58
+ "-----------|---------",
59
+ "LONG | LASTNAME",
60
+ "-----------|---------",
61
+ "SomeVer... | Doe ",
62
+ "-----------|---------",
63
+ ""
64
+ ].join("\n")
65
+
66
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
67
+ end
68
+
69
+ it "truncates string when it exceeds width" do
70
+ first_field = Fields::Field.new(:path => [:long], :label => "Long", :width => 10)
71
+ fields = [first_field, field_lastname]
72
+
73
+ expected_output = [
74
+ "-----------|---------",
75
+ "LONG | LASTNAME",
76
+ "-----------|---------",
77
+ "SomeVer... | Doe ",
78
+ "-----------|---------",
79
+ ""
80
+ ].join("\n")
81
+
82
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
83
+ end
84
+
85
+ it "sets certain width" do
86
+ first_field = Fields::Field.new(:path => [:long], :label => "Long", :width => 25)
87
+ fields = [first_field, field_lastname]
88
+
89
+ expected_output = [
90
+ "--------------------------|---------",
91
+ "LONG | LASTNAME",
92
+ "--------------------------|---------",
93
+ "SomeVeryLongString | Doe ",
94
+ "--------------------------|---------",
95
+ ""
96
+ ].join("\n")
97
+
98
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
99
+ end
100
+
101
+ it "gives preference to width over maximal width" do
102
+ first_field = Fields::Field.new(:path => [:long], :label => "Long", :width => 25, :max_width => 10)
103
+ fields = [first_field, field_lastname]
104
+
105
+ expected_output = [
106
+ "--------------------------|---------",
107
+ "LONG | LASTNAME",
108
+ "--------------------------|---------",
109
+ "SomeVeryLongString | Doe ",
110
+ "--------------------------|---------",
111
+ ""
112
+ ].join("\n")
113
+
114
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
115
+ end
116
+
117
+ end
118
+
43
119
  context "formatters" do
44
120
  it "should apply formatters" do
45
121
  class DotFormatter < HammerCLI::Output::Formatters::FieldFormatter
@@ -48,28 +124,36 @@ describe HammerCLI::Output::Adapter::Table do
48
124
  end
49
125
  end
50
126
 
51
- adapter = HammerCLI::Output::Adapter::Table.new({}, { :DataField => [ DotFormatter.new ]})
127
+ adapter = HammerCLI::Output::Adapter::Table.new({}, { :Field => [ DotFormatter.new ]})
52
128
  out, err = capture_io { adapter.print_collection(fields, data) }
53
129
  out.must_match(/.*-DOT-.*/)
54
130
  end
55
131
  end
56
132
 
57
133
  context "sort_columns" do
58
- let(:field_firstname) { Fields::DataField.new(:path => [:firstname], :label => "Firstname") }
59
- let(:field_lastname) { Fields::DataField.new(:path => [:lastname], :label => "Lastname") }
60
134
  let(:fields) {
61
135
  [field_firstname, field_lastname]
62
136
  }
63
- let(:data) { HammerCLI::Output::RecordCollection.new [{
64
- :firstname => "John",
65
- :lastname => "Doe"
66
- }]}
67
137
 
68
138
  it "should sort output" do
69
- TablePrint::Printer.any_instance.stubs(:table_print).returns(
70
- "LASTNAME | FIRSTNAME\n---------|----------\nDoe | John \n")
71
- proc { adapter.print_collection(fields, data) }.must_output(
72
- "----------|---------\nFIRSTNAME | LASTNAME\n----------|---------\nJohn | Doe \n----------|---------\n")
139
+
140
+ table_print_output = [
141
+ "LASTNAME | FIRSTNAME",
142
+ "---------|----------",
143
+ "Doe | John "
144
+ ].join("\n")
145
+
146
+ expected_output = [
147
+ "----------|---------",
148
+ "FIRSTNAME | LASTNAME",
149
+ "----------|---------",
150
+ "John | Doe ",
151
+ "----------|---------",
152
+ ""
153
+ ].join("\n")
154
+
155
+ TablePrint::Printer.any_instance.stubs(:table_print).returns(table_print_output)
156
+ proc { adapter.print_collection(fields, data) }.must_output(expected_output)
73
157
  end
74
158
  end
75
159
  end