haveapi-client 0.3.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c2c9e15337f0d495ac86049fee2e76cf545957c
4
- data.tar.gz: 9eaf5f13ad4c6d128a55b7efb3795b2840dd64dd
3
+ metadata.gz: 6beb148ebd5b4d6314016309a7fa9eb52292b78f
4
+ data.tar.gz: 54ff60ff22142d49eaa75cd27140f3349528b066
5
5
  SHA512:
6
- metadata.gz: 7fa14b35b737f100c15ae7c8091e2abb18f9f8b3286ac006e723484f2c967dbb9f0683606fc7cda477c6cbc7d34ed9aabbe9bba83200a01608c3a9b04c825589
7
- data.tar.gz: b3ac301b6d97864c2a1ddf909a121efb23aa5ef419be24f9e7ab0a436c16085f156f23ebaff4ed394f67c12665685323c70a7e3e95e147f672a768dc589130c7
6
+ metadata.gz: 2c5f0dfbd4fce722a9ec0b02cf175d351581cfba4d2142c8304059fd8cea0d052a3d3467ceda55e5647c8d2834540c4b17fd4eb49edf9486645ebff55be4c87a
7
+ data.tar.gz: fe3dc2e629ee1505eae68a36632de3a1edd2b9b8e0daf23fc0f61540fd4165ecac69c36226c25eb6ef21f0dce5dcd4aa7c1c9bdcfd192e12388f42e75a11d552
@@ -0,0 +1,17 @@
1
+ * Web Jan 20 2016 - version 0.4.0
2
+ - API for accessing authentication token
3
+ - Client-side input parameter validators
4
+ - Drop table_print in favor of custom output formatter
5
+ - CLI
6
+ - switches -c, --columns, -r, --rows to select output mode
7
+ - option -o, --output to select parameters to print
8
+ - option -s, --sort to sort by specific parameter (on client-side)
9
+ - switch --timestamp, --utc and --localtime to select how to print Datetime
10
+ parameters
11
+ - switch --check-compatibility to determine whether the client is compatible
12
+ with given API server
13
+ - switch --protocol-version to print protocl version that this clients
14
+ implements
15
+ - Check protocol version
16
+ - Fixed check of invalid action
17
+ - ResourceInstance passes on prepared_args correctly
data/README.md CHANGED
@@ -28,10 +28,22 @@ Or install it yourself as:
28
28
  --list-resources [VERSION] List all resource in API version
29
29
  --list-actions [VERSION] List all resources and actions in API version
30
30
  --version VERSION Use specified API version
31
- -r, --raw Print raw response as is
32
- -s, --save Save credentials to config file for later use
31
+ -c, --columns Print output in columns
32
+ -H, --no-header Hide header row
33
+ -L, --list-parameters List output parameters
34
+ -o, --output PARAMETERS Parameters to display, separated by a comma
35
+ -r, --rows Print output in rows
36
+ -s, --sort PARAMETER Sort output by parameter
37
+ --save Save credentials to config file for later use
38
+ --raw Print raw response as is
39
+ --timestamp Display Datetime parameters as timestamp
40
+ --utc Display Datetime parameters in UTC
41
+ --localtime Display Datetime parameters in local timezone
42
+ --date-format FORMAT Display Datetime in custom format
33
43
  -v, --[no-]verbose Run verbosely
34
44
  --client-version Show client version
45
+ --protocol-version Show protocol version
46
+ --check-compatibility Check compatibility with API server
35
47
  -h, --help Show this message
36
48
 
37
49
  Using the API example from
@@ -6,6 +6,7 @@ require 'haveapi/client/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'haveapi-client'
8
8
  spec.version = HaveAPI::Client::VERSION
9
+ spec.date = '2016-01-20'
9
10
  spec.authors = ['Jakub Skokan']
10
11
  spec.email = ['jakub.skokan@vpsfree.cz']
11
12
  spec.summary =
@@ -21,10 +22,9 @@ Gem::Specification.new do |spec|
21
22
  spec.add_development_dependency 'bundler', '~> 1.6'
22
23
  spec.add_development_dependency 'rake'
23
24
 
24
- spec.add_runtime_dependency 'activesupport', '~> 4.1.0'
25
- spec.add_runtime_dependency 'require_all', '~> 1.3.0'
25
+ spec.add_runtime_dependency 'activesupport', '~> 4.1.14'
26
+ spec.add_runtime_dependency 'require_all', '~> 1.3.3'
26
27
  spec.add_runtime_dependency 'rest_client', '~> 1.7.3'
27
- spec.add_runtime_dependency 'json', '~> 1.8.1'
28
- spec.add_runtime_dependency 'highline', '~> 1.6.21'
29
- spec.add_runtime_dependency 'table_print', '~> 1.5.3'
28
+ spec.add_runtime_dependency 'json', '~> 1.8.3'
29
+ spec.add_runtime_dependency 'highline', '~> 1.7.8'
30
30
  end
@@ -1,2 +1,7 @@
1
1
  require_relative 'client'
2
+
3
+ module HaveAPI
4
+ module CLI ; end
5
+ end
6
+
2
7
  require_rel 'cli'
@@ -1,428 +1,679 @@
1
1
  require 'optparse'
2
2
  require 'pp'
3
3
  require 'highline/import'
4
- require 'table_print'
5
4
  require 'yaml'
5
+ require 'time'
6
6
 
7
- module HaveAPI
8
- module CLI
9
- class Cli
10
- class << self
11
- attr_accessor :auth_methods
7
+ module HaveAPI::CLI
8
+ class Cli
9
+ class << self
10
+ attr_accessor :auth_methods, :commands
12
11
 
13
- def run
14
- c = new
15
- end
12
+ def run
13
+ c = new
14
+ end
16
15
 
17
- def register_auth_method(name, klass)
18
- @auth_methods ||= {}
19
- @auth_methods[name] = klass
20
- end
16
+ def register_auth_method(name, klass)
17
+ @auth_methods ||= {}
18
+ @auth_methods[name] = klass
21
19
  end
22
20
 
23
- def initialize
24
- @config = read_config || {}
25
- args, @opts = options
21
+ def register_command(cmd)
22
+ @commands ||= []
23
+ @commands << cmd
24
+ end
25
+ end
26
26
 
27
- @api = HaveAPI::Client::Communicator.new(api_url, @opts[:version])
28
- @api.identity = $0.split('/').last
27
+ def initialize
28
+ @config = read_config || {}
29
+ args, @opts = options
29
30
 
30
- if @action
31
- method(@action.first).call( * @action[1..-1] )
32
- exit
33
- end
31
+ @api = HaveAPI::Client::Communicator.new(api_url, @opts[:version])
32
+ @api.identity = $0.split('/').last
34
33
 
35
- if (@opts[:help] && args.empty?) || args.empty?
36
- puts @global_opt.help
34
+ if @action
35
+ method(@action.first).call( * @action[1..-1] )
36
+ exit
37
+ end
38
+
39
+ if (@opts[:help] && args.empty?) || args.empty?
40
+ show_help do
37
41
  puts "\nAvailable resources:"
38
42
  list_resources
39
- exit(true)
40
43
  end
44
+ end
41
45
 
42
- resources = args[0].split('.')
46
+ resources = args[0].split('.')
43
47
 
44
- if args.count == 1
45
- describe_resource(resources)
46
- exit(true)
48
+ if cmd = find_command(resources, args[1])
49
+ authenticate if @auth
50
+ c = cmd.new(@opts, HaveAPI::Client::Client.new(nil, communicator: @api))
51
+
52
+ cmd_opt = OptionParser.new do |opts|
53
+ opts.banner = "\nCommand options:"
54
+ c.options(opts)
55
+
56
+ opts.on('-h', '--help', 'Show this message') do
57
+ show_help do
58
+ puts cmd_opt.help
59
+ end
60
+ end
61
+ end
62
+
63
+ if @opts[:help]
64
+ show_help do
65
+ puts cmd_opt.help
66
+ end
67
+ end
68
+
69
+ if sep = ARGV.index('--')
70
+ cmd_opt.parse!(ARGV[sep+1..-1])
47
71
  end
48
72
 
49
- action = @api.get_action(resources, args[1].to_sym, args[2..-1])
50
- action.update_description(@api.describe_action(action)) if authenticate(action)
73
+ c.exec(args[2..-1] || [])
51
74
 
52
- @input_params = parameters(action)
75
+ exit
76
+ end
53
77
 
54
- if action
55
- unless params_valid?(action)
56
- warn 'Missing required parameters'
57
- end
78
+ if args.count == 1
79
+ describe_resource(resources)
80
+ exit(true)
81
+ end
58
82
 
59
- ret = action.execute(@input_params, raw: @opts[:raw])
83
+ action = @api.get_action(resources, args[1].to_sym, args[2..-1])
60
84
 
61
- if ret[:status]
62
- format_output(action, ret[:response])
85
+ unless action
86
+ warn "Resource or action '#{args[0]} #{args[1]}' not found"
87
+ puts
88
+ show_help(false)
89
+ end
63
90
 
64
- else
65
- warn "Action failed: #{ret[:message]}"
91
+ action.update_description(@api.describe_action(action)) if authenticate(action)
66
92
 
67
- if ret[:errors] && ret[:errors].any?
68
- puts 'Errors:'
69
- ret[:errors].each do |param, e|
70
- puts "\t#{param}: #{e.join('; ')}"
71
- end
72
- end
93
+ @selected_params = @opts[:output] ? @opts[:output].split(',').uniq
94
+ : nil
73
95
 
74
- print_examples(action)
75
- end
96
+ @input_params = parameters(action)
76
97
 
77
- else
78
- warn "Action #{ARGV[0]}##{ARGV[1]} not valid"
79
- exit(false)
80
- end
98
+ includes = build_includes(action) if @selected_params
99
+ @input_params[:meta] = { includes: includes } if includes
100
+
101
+ begin
102
+ ret = action.execute(@input_params, raw: @opts[:raw])
103
+
104
+ rescue HaveAPI::Client::ValidationError => e
105
+ format_errors(action, 'input parameters not valid', e.errors)
106
+ exit(false)
81
107
  end
82
108
 
83
- def api_url
84
- @opts[:client]
109
+ if ret[:status]
110
+ format_output(action, ret[:response])
111
+
112
+ else
113
+ format_errors(action, ret[:message], ret[:errors])
114
+ exit(false)
85
115
  end
116
+ end
86
117
 
87
- def options
88
- options = {
89
- client: default_url,
90
- verbose: false,
91
- }
118
+ def api_url
119
+ @opts[:client]
120
+ end
92
121
 
93
- @global_opt = OptionParser.new do |opts|
94
- opts.banner = "Usage: #{$0} [options] <resource> <action> [objects ids] [-- [parameters]]"
122
+ def options
123
+ options = {
124
+ client: default_url,
125
+ verbose: false,
126
+ }
95
127
 
96
- opts.on('-u', '--api URL', 'API URL') do |url|
97
- options[:client] = url
98
- end
128
+ @global_opt = OptionParser.new do |opts|
129
+ opts.banner = "Usage: #{$0} [options] <resource> <action> [objects ids] [-- [parameters]]"
99
130
 
100
- opts.on('-a', '--auth METHOD', Cli.auth_methods.keys, 'Authentication method') do |m|
101
- options[:auth] = m
102
- @auth = Cli.auth_methods[m].new(server_config(options[:client])[:auth][m])
103
- @auth.options(opts)
104
- end
131
+ opts.on('-u', '--api URL', 'API URL') do |url|
132
+ options[:client] = url
133
+ end
105
134
 
106
- opts.on('--list-versions', 'List all available API versions') do
107
- @action = [:list_versions]
108
- end
135
+ opts.on('-a', '--auth METHOD', Cli.auth_methods.keys, 'Authentication method') do |m|
136
+ options[:auth] = m
137
+ @auth = Cli.auth_methods[m].new(server_config(options[:client])[:auth][m])
138
+ @auth.options(opts)
139
+ end
109
140
 
110
- opts.on('--list-auth-methods [VERSION]', 'List available authentication methods') do |v|
111
- @action = [:list_auth, v && v.sub(/^v/, '')]
112
- end
141
+ opts.on('--list-versions', 'List all available API versions') do
142
+ @action = [:list_versions]
143
+ end
113
144
 
114
- opts.on('--list-resources [VERSION]', 'List all resource in API version') do |v|
115
- @action = [:list_resources, v && v.sub(/^v/, '')]
116
- end
145
+ opts.on('--list-auth-methods [VERSION]', 'List available authentication methods') do |v|
146
+ @action = [:list_auth, v && v.sub(/^v/, '')]
147
+ end
117
148
 
118
- opts.on('--list-actions [VERSION]', 'List all resources and actions in API version') do |v|
119
- @action = [:list_actions, v && v.sub(/^v/, '')]
120
- end
149
+ opts.on('--list-resources [VERSION]', 'List all resource in API version') do |v|
150
+ @action = [:list_resources, v && v.sub(/^v/, '')]
151
+ end
121
152
 
122
- opts.on('--version VERSION', 'Use specified API version') do |v|
123
- options[:version] = v
124
- end
153
+ opts.on('--list-actions [VERSION]', 'List all resources and actions in API version') do |v|
154
+ @action = [:list_actions, v && v.sub(/^v/, '')]
155
+ end
125
156
 
126
- opts.on('-r', '--raw', 'Print raw response as is') do
127
- options[:raw] = true
128
- end
157
+ opts.on('--version VERSION', 'Use specified API version') do |v|
158
+ options[:version] = v
159
+ end
129
160
 
130
- opts.on('-s', '--save', 'Save credentials to config file for later use') do
131
- options[:save] = true
132
- end
161
+ opts.on('-c', '--columns', 'Print output in columns') do
162
+ options[:layout] = :columns
163
+ end
133
164
 
134
- opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
135
- options[:verbose] = v
136
- end
165
+ opts.on('-H', '--no-header', 'Hide header row') do |h|
166
+ options[:header] = false
167
+ end
137
168
 
138
- opts.on('--client-version', 'Show client version') do
139
- @action = [:show_version]
140
- end
169
+ opts.on('-L', '--list-parameters', 'List output parameters') do |l|
170
+ options[:list_output] = true
171
+ end
141
172
 
142
- opts.on('-h', '--help', 'Show this message') do
143
- options[:help] = true
144
- end
173
+ opts.on('-o', '--output PARAMETERS', 'Parameters to display, separated by a comma') do |o|
174
+ options[:output] = o
145
175
  end
146
176
 
147
- args = []
177
+ opts.on('-r', '--rows', 'Print output in rows') do
178
+ options[:layout] = :rows
179
+ end
180
+
181
+ opts.on('-s', '--sort PARAMETER', 'Sort output by parameter') do |p|
182
+ options[:sort] = p
183
+ end
148
184
 
149
- ARGV.each do |arg|
150
- if arg == '--'
151
- break
152
- else
153
- args << arg
154
- end
185
+ opts.on('--save', 'Save credentials to config file for later use') do
186
+ options[:save] = true
187
+ end
188
+
189
+ opts.on('--raw', 'Print raw response as is') do
190
+ options[:raw] = true
191
+ end
192
+
193
+ opts.on('--timestamp', 'Display Datetime parameters as timestamp') do
194
+ options[:datetime] = :timestamp
195
+ end
196
+
197
+ opts.on('--utc', 'Display Datetime parameters in UTC') do
198
+ options[:datetime] = :utc
199
+ end
200
+
201
+ opts.on('--localtime', 'Display Datetime parameters in local timezone') do
202
+ options[:datetime] = :local
203
+ end
204
+
205
+ opts.on('--date-format FORMAT', 'Display Datetime in custom format') do |f|
206
+ options[:date_format] = f
155
207
  end
156
208
 
157
- @global_opt.parse!(args)
209
+ opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
210
+ options[:verbose] = v
211
+ end
158
212
 
159
- unless options[:auth]
160
- cfg = server_config(options[:client])
213
+ opts.on('--client-version', 'Show client version') do
214
+ @action = [:show_version]
215
+ end
216
+
217
+ opts.on('--protocol-version', 'Show protocol version') do
218
+ @action = [:protocol_version]
219
+ end
220
+
221
+ opts.on('--check-compatibility', 'Check compatibility with API server') do
222
+ @action = [:check_compat]
223
+ end
161
224
 
162
- @auth = Cli.auth_methods[cfg[:last_auth]].new(cfg[:auth][cfg[:last_auth]]) if cfg[:last_auth]
225
+ opts.on('-h', '--help', 'Show this message') do
226
+ options[:help] = true
163
227
  end
228
+ end
164
229
 
165
- [args, options]
230
+ args = []
231
+
232
+ ARGV.each do |arg|
233
+ if arg == '--'
234
+ break
235
+ else
236
+ args << arg
237
+ end
166
238
  end
167
239
 
168
- def parameters(action)
169
- options = {}
170
- sep = ARGV.index('--')
240
+ @global_opt.parse!(args)
171
241
 
172
- @action_opt = OptionParser.new do |opts|
173
- opts.banner = ''
242
+ unless options[:auth]
243
+ cfg = server_config(options[:client])
174
244
 
175
- action.input[:parameters].each do |name, p|
176
- opts.on(param_option(name, p), p[:description] || p[:label] || '') do |*args|
177
- options[name] = args.first
178
- end
179
- end
245
+ @auth = Cli.auth_methods[cfg[:last_auth]].new(cfg[:auth][cfg[:last_auth]]) if cfg[:last_auth]
246
+ end
180
247
 
181
- opts.on('-h', '--help', 'Show this message') do
182
- @opts[:help] = true
248
+ [args, options]
249
+ end
250
+
251
+ def parameters(action)
252
+ options = {}
253
+ sep = ARGV.index('--')
254
+
255
+ @action_opt = OptionParser.new do |opts|
256
+ opts.banner = ''
257
+
258
+ action.input[:parameters].each do |name, p|
259
+ opts.on(param_option(name, p), p[:description] || p[:label] || '') do |*args|
260
+ options[name] = args.first
183
261
  end
184
262
  end
185
263
 
186
- if @opts[:help]
187
- puts @global_opt.help
188
- puts ''
264
+ opts.on('-h', '--help', 'Show this message') do
265
+ @opts[:help] = true
266
+ end
267
+ end
268
+
269
+ if @opts[:help]
270
+ show_help do
189
271
  puts 'Action description:'
190
272
  puts action.description, "\n"
191
- print 'Action parameters:'
273
+ print 'Input parameters:'
192
274
  puts @action_opt.help
275
+ puts
276
+ puts 'Output parameters:'
277
+
278
+ action.params.each do |name, param|
279
+ puts sprintf(" %-32s %s", name, param[:description])
280
+ end
281
+
193
282
  print_examples(action)
194
- exit
195
283
  end
284
+ end
196
285
 
197
- return {} unless sep
286
+ if @opts[:list_output]
287
+ action.params.each_key { |name| puts name }
288
+ exit
289
+ end
198
290
 
199
- @action_opt.parse!(ARGV[sep+1..-1])
291
+ return {} unless sep
200
292
 
201
- options
202
- end
293
+ @action_opt.parse!(ARGV[sep+1..-1])
203
294
 
204
- def param_option(name, p)
205
- ret = '--'
206
- name = name.to_s.dasherize
295
+ options
296
+ end
207
297
 
208
- if p[:type] == 'Boolean'
209
- ret += "[no-]#{name}"
298
+ def param_option(name, p)
299
+ ret = '--'
300
+ name = name.to_s.dasherize
210
301
 
211
- else
212
- ret += "#{name} #{name.underscore.upcase}"
213
- end
302
+ if p[:type] == 'Boolean'
303
+ ret += "[no-]#{name}"
214
304
 
215
- ret
305
+ else
306
+ ret += "#{name} #{name.underscore.upcase}"
216
307
  end
217
308
 
218
- def list_versions
219
- desc = @api.available_versions
309
+ ret
310
+ end
220
311
 
221
- desc[:versions].each do |v, _|
222
- next if v == :default
312
+ def list_versions
313
+ desc = @api.available_versions
223
314
 
224
- v_int = v.to_s.to_i
315
+ desc[:versions].each do |v, _|
316
+ next if v == :default
225
317
 
226
- puts "#{v_int == desc[:default] ? '*' : ' '} v#{v}"
227
- end
318
+ v_int = v.to_s.to_i
319
+
320
+ puts "#{v_int == desc[:default] ? '*' : ' '} v#{v}"
228
321
  end
322
+ end
229
323
 
230
- def list_auth(v=nil)
231
- desc = @api.describe_api(v)
324
+ def list_auth(v=nil)
325
+ desc = @api.describe_api(v)
232
326
 
233
- desc[:authentication].each_key do |auth|
234
- puts auth if Cli.auth_methods.has_key?(auth)
235
- end
327
+ desc[:authentication].each_key do |auth|
328
+ puts auth if Cli.auth_methods.has_key?(auth)
236
329
  end
330
+ end
237
331
 
238
- def list_resources(v=nil)
239
- desc = @api.describe_api(v)
332
+ def list_resources(v=nil)
333
+ desc = @api.describe_api(v)
240
334
 
241
- desc[:resources].each do |resource, children|
242
- nested_resource(resource, children, false)
243
- end
335
+ desc[:resources].each do |resource, children|
336
+ nested_resource(resource, children, false)
244
337
  end
338
+ end
245
339
 
246
- def list_actions(v=nil)
247
- desc = @api.describe_api(v)
340
+ def list_actions(v=nil)
341
+ desc = @api.describe_api(v)
248
342
 
249
- desc[:resources].each do |resource, children|
250
- nested_resource(resource, children, true)
251
- end
343
+ desc[:resources].each do |resource, children|
344
+ nested_resource(resource, children, true)
252
345
  end
346
+ end
253
347
 
254
- def show_version
255
- puts HaveAPI::Client::VERSION
348
+ def show_version
349
+ puts HaveAPI::Client::VERSION
350
+ end
351
+
352
+ def protocol_version
353
+ puts HaveAPI::Client::PROTOCOL_VERSION
354
+ end
355
+
356
+ def check_compat
357
+ case @api.compatible?
358
+ when :compatible
359
+ puts 'compatible'
360
+ exit
361
+
362
+ when :imperfect
363
+ puts 'imperfect'
364
+ exit(1)
365
+
366
+ else
367
+ puts 'incompatible'
368
+ exit(2)
256
369
  end
370
+ end
257
371
 
258
- def describe_resource(path)
259
- desc = @api.describe_resource(path)
372
+ def describe_resource(path)
373
+ desc = @api.describe_resource(path)
260
374
 
261
- unless desc
262
- warn "Resource #{path.join('.')} does not exist"
263
- exit(false)
264
- end
375
+ unless desc
376
+ warn "Resource #{path.join('.')} does not exist"
377
+ exit(false)
378
+ end
265
379
 
266
- unless desc[:resources].empty?
267
- puts 'Resources:'
380
+ unless desc[:resources].empty?
381
+ puts 'Resources:'
268
382
 
269
- desc[:resources].each_key do |r|
270
- puts " #{r}"
271
- end
383
+ desc[:resources].each_key do |r|
384
+ puts " #{r}"
272
385
  end
386
+ end
273
387
 
274
- puts '' if !desc[:resources].empty? && !desc[:actions].empty?
388
+ puts '' if !desc[:resources].empty? && !desc[:actions].empty?
275
389
 
276
- unless desc[:actions].empty?
277
- puts 'Actions:'
390
+ unless desc[:actions].empty?
391
+ puts 'Actions:'
278
392
 
279
- desc[:actions].each_key do |a|
280
- puts " #{a}"
281
- end
393
+ desc[:actions].each_key do |a|
394
+ puts " #{a}"
282
395
  end
283
396
  end
397
+ end
284
398
 
285
- def nested_resource(prefix, children, actions=false)
286
- if actions
287
- children[:actions].each do |action, _|
288
- puts "#{prefix}##{action}"
289
- end
290
- else
291
- puts prefix
399
+ def nested_resource(prefix, children, actions=false)
400
+ if actions
401
+ children[:actions].each do |action, _|
402
+ puts "#{prefix}##{action}"
292
403
  end
404
+ else
405
+ puts prefix
406
+ end
293
407
 
294
- children[:resources].each do |resource, children|
295
- nested_resource("#{prefix}.#{resource}", children, actions)
296
- end
408
+ children[:resources].each do |resource, children|
409
+ nested_resource("#{prefix}.#{resource}", children, actions)
297
410
  end
411
+ end
298
412
 
299
- def print_examples(action)
300
- unless action.examples.empty?
301
- puts "\nExamples:\n"
302
- ExampleFormatter.format_examples(self, action)
413
+ def show_help(exit_code = true)
414
+ puts @global_opt.help
415
+
416
+ if Cli.commands
417
+ puts
418
+ puts 'Commands:'
419
+ Cli.commands.each do |cmd|
420
+ puts sprintf(
421
+ '%-36s %s',
422
+ "#{cmd.resource.join('.')} #{cmd.action} #{cmd.args}",
423
+ cmd.desc
424
+ )
303
425
  end
304
426
  end
305
427
 
306
- def format_output(action, response, out = $>)
307
- if @opts[:raw]
308
- puts response
309
- return
310
- end
428
+ yield if block_given?
429
+ exit(exit_code)
430
+ end
311
431
 
312
- return if response.empty?
432
+ def print_examples(action)
433
+ unless action.examples.empty?
434
+ puts "\nExamples:\n"
435
+ ExampleFormatter.format_examples(self, action)
436
+ end
437
+ end
313
438
 
314
- tp.set :io, out
439
+ def format_output(action, response, out = $>)
440
+ if @opts[:raw]
441
+ puts JSON.generate(response)
442
+ return
443
+ end
315
444
 
316
- namespace = action.namespace(:output).to_sym
445
+ return if response.empty?
317
446
 
318
- case action.output_layout.to_sym
319
- when :object_list, :hash_list
320
- cols = []
447
+ namespace = action.namespace(:output).to_sym
321
448
 
322
- action.params.each do |name, p|
323
- if p[:type] == 'Resource'
324
- cols << {
325
- name => {
326
- display_method: ->(r) {
327
- r[name] && "#{r[name][p[:value_label].to_sym]} (##{r[name][p[:value_id].to_sym]})"
328
- }
329
- }
330
- }
331
- else
332
- cols << name
333
- end
449
+ if action.output_layout.to_sym == :custom
450
+ return PP.pp(response[namespace], out)
451
+ end
452
+
453
+ cols = []
454
+
455
+ (@selected_params || action.params.keys).each do |raw_name|
456
+ col = {}
457
+ name = nil
458
+ param = nil
459
+
460
+ # Fetching an associated attribute
461
+ if raw_name.to_s.index('.')
462
+ parts = raw_name.to_s.split('.').map! { |v| v.to_sym }
463
+ name = parts.first.to_sym
464
+
465
+ top = action.params
466
+
467
+ parts.each do |part|
468
+ fail "'#{part}' not found" unless top.has_key?(part)
469
+
470
+ if top[part][:type] == 'Resource'
471
+ param = top[part]
472
+ top = @api.get_action(top[part][:resource], :show, []).params
473
+
474
+ else
475
+ param = top[part]
476
+ break
334
477
  end
478
+ end
335
479
 
336
- tp response[namespace], *cols
480
+ col[:display] = Proc.new do |r|
481
+ next '' unless r
337
482
 
483
+ top = r
484
+ parts[1..-1].each do |part|
485
+ fail "'#{part}' not found" unless top.has_key?(part)
486
+ break if top[part].nil?
487
+ top = top[part]
488
+ end
489
+
490
+ case param[:type]
491
+ when 'Resource'
492
+ "#{top[ param[:value_label].to_sym ]} (##{top[ param[:value_id].to_sym ]})"
338
493
 
339
- when :object, :hash
340
- response[namespace].each do |k, v|
494
+ when 'Datetime'
495
+ format_date(top)
341
496
 
342
- if action.params[k][:type] == 'Resource'
343
- out << "#{k}: #{v[action.params[k][:value_label].to_sym]}\n"
344
- else
345
- out << "#{k}: #{v}\n"
346
- end
497
+ else
498
+ top
347
499
  end
500
+ end
348
501
 
502
+ col[:label] = raw_name
503
+
504
+ else # directly accessible parameter
505
+ name = raw_name.to_sym
506
+ param = action.params[name]
507
+ fail "parameter '#{name}' does not exist" if param.nil?
508
+
509
+ if param[:type] == 'Resource'
510
+ col[:display] = Proc.new do |r|
511
+ next '' unless r
512
+ "#{r[ param[:value_label].to_sym ]} (##{r[ param[:value_id].to_sym ]})"
513
+ end
349
514
 
350
- when :custom
351
- PP.pp(response[namespace], out)
352
-
515
+ elsif param[:type] == 'Datetime'
516
+ col[:display] = ->(date) { format_date(date) }
517
+ end
353
518
  end
519
+
520
+ col.update({
521
+ name: name,
522
+ align: %w(Integer Float).include?(param[:type]) ? 'right' : 'left',
523
+ })
524
+
525
+ col[:label] ||= param[:label] && !param[:label].empty? ? param[:label] : name.upcase
526
+
527
+ cols << col
354
528
  end
355
529
 
356
- def header_for(action, param)
357
- params = action.params
530
+ OutputFormatter.print(
531
+ response[namespace],
532
+ cols,
533
+ header: @opts[:header].nil?,
534
+ sort: @opts[:sort] && @opts[:sort].to_sym,
535
+ layout: @opts[:layout]
536
+ )
537
+ end
358
538
 
359
- if params.has_key?(param) && params[param][:label]
360
- params[param][:label]
361
- else
362
- param.to_s.upcase
363
- end
539
+ def header_for(action, param)
540
+ params = action.params
541
+
542
+ if params.has_key?(param) && params[param][:label]
543
+ params[param][:label]
544
+ else
545
+ param.to_s.upcase
364
546
  end
547
+ end
365
548
 
366
- def authenticate(action)
367
- if action.auth?
368
- if @auth
369
- @auth.communicator = @api
370
- @auth.validate
371
- @auth.authenticate
549
+ def authenticate(action = nil)
550
+ return false if !action.nil? && !action.auth?
372
551
 
373
- if @opts[:save]
374
- cfg = server_config(api_url)
375
- cfg[:auth][@opts[:auth]] = @auth.save
376
- cfg[:last_auth] = @opts[:auth]
377
- write_config
378
- end
379
-
380
- else
381
- # FIXME: exit as auth is needed and has not been selected
382
- end
552
+ if @auth
553
+ @auth.communicator = @api
554
+ @auth.validate
555
+ @auth.authenticate
383
556
 
384
- return true
557
+ if @opts[:save]
558
+ cfg = server_config(api_url)
559
+ cfg[:auth][@opts[:auth]] = @auth.save
560
+ cfg[:last_auth] = @opts[:auth]
561
+ write_config
385
562
  end
386
563
 
387
- false
564
+ else
565
+ # FIXME: exit as auth is needed and has not been selected
388
566
  end
389
567
 
390
- def params_valid?(action)
391
- true # FIXME
568
+ return true
569
+ end
570
+
571
+ protected
572
+ def default_url
573
+ 'http://localhost:4567'
574
+ end
575
+
576
+ def config_path
577
+ "#{Dir.home}/.haveapi-client.yml"
578
+ end
579
+
580
+ def write_config
581
+ File.open(config_path, 'w') do |f|
582
+ f.write(YAML.dump(@config))
392
583
  end
584
+ end
585
+
586
+ def read_config
587
+ @config = YAML.load_file(config_path) if File.exists?(config_path)
588
+ end
393
589
 
394
- protected
395
- def default_url
396
- 'http://localhost:4567'
590
+ def server_config(url)
591
+ unless @config[:servers]
592
+ @config[:servers] = [{url: url, auth: {}}]
593
+ return @config[:servers].first
397
594
  end
398
595
 
399
- def config_path
400
- "#{Dir.home}/.haveapi-client.yml"
596
+ @config[:servers].each do |s|
597
+ return s if s[:url] == url
401
598
  end
402
599
 
403
- def write_config
404
- File.open(config_path, 'w') do |f|
405
- f.write(YAML.dump(@config))
600
+ @config[:servers] << {url: url, auth: {}}
601
+ @config[:servers].last
602
+ end
603
+
604
+ def format_errors(action, msg, errors)
605
+ warn "Action failed: #{msg}"
606
+
607
+ if errors.any?
608
+ puts 'Errors:'
609
+ errors.each do |param, e|
610
+ puts "\t#{param}: #{e.join('; ')}"
406
611
  end
407
612
  end
408
613
 
409
- def read_config
410
- @config = YAML.load_file(config_path) if File.exists?(config_path)
614
+ print_examples(action)
615
+ end
616
+
617
+ def find_command(resource, action)
618
+ return false unless Cli.commands
619
+
620
+ Cli.commands.each do |cmd|
621
+ return cmd if cmd.handle?(resource, action)
411
622
  end
412
623
 
413
- def server_config(url)
414
- unless @config[:servers]
415
- @config[:servers] = [{url: url, auth: {}}]
416
- return @config[:servers].first
417
- end
624
+ false
625
+ end
418
626
 
419
- @config[:servers].each do |s|
420
- return s if s[:url] == url
627
+ # Translate requested parameters into meta[includes] that is sent
628
+ # to the API.
629
+ #
630
+ # When using haveapi-cli vps list -o
631
+ # node.location => node__location
632
+ # node.location.domain => node_location
633
+ # node.name => node
634
+ def build_includes(action)
635
+ ret = []
636
+
637
+ @selected_params.each do |param|
638
+ next unless param.index('.')
639
+
640
+ includes = []
641
+ top = action.params
642
+
643
+ param.split('.').map! { |v| v.to_sym }.each do |part|
644
+ next unless top.has_key?(part)
645
+ next if top[part][:type] != 'Resource'
646
+
647
+ includes << part
648
+ top = @api.get_action(top[part][:resource], :show, []).params
421
649
  end
422
650
 
423
- @config[:servers] << {url: url, auth: {}}
424
- @config[:servers].last
651
+ ret << includes.join('__')
652
+ end
653
+
654
+ ret.uniq!
655
+ ret.empty? ? nil : ret.join(',')
656
+ end
657
+
658
+ def format_date(date)
659
+ return '' unless date
660
+
661
+ t = Time.iso8601(date)
662
+ ret = case @opts[:datetime]
663
+ when :timestamp
664
+ t.to_i
665
+
666
+ when :utc
667
+ t.utc
668
+
669
+ when :local
670
+ t.localtime
671
+
672
+ else
673
+ t.localtime
425
674
  end
675
+
676
+ @opts[:date_format] ? ret.strftime(@opts[:date_format]) : ret
426
677
  end
427
678
  end
428
679
  end