hammer_cli 0.0.18 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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