haveapi-client 0.3.0 → 0.4.0

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