morpheus-cli 4.2.14 → 4.2.19

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/README.md +8 -6
  4. data/lib/morpheus/api/api_client.rb +32 -14
  5. data/lib/morpheus/api/auth_interface.rb +4 -2
  6. data/lib/morpheus/api/backup_jobs_interface.rb +9 -0
  7. data/lib/morpheus/api/backups_interface.rb +16 -0
  8. data/lib/morpheus/api/deploy_interface.rb +25 -56
  9. data/lib/morpheus/api/deployments_interface.rb +44 -55
  10. data/lib/morpheus/api/doc_interface.rb +57 -0
  11. data/lib/morpheus/api/instances_interface.rb +5 -0
  12. data/lib/morpheus/api/rest_interface.rb +40 -0
  13. data/lib/morpheus/api/user_sources_interface.rb +0 -15
  14. data/lib/morpheus/api/users_interface.rb +2 -3
  15. data/lib/morpheus/benchmarking.rb +2 -2
  16. data/lib/morpheus/cli.rb +4 -1
  17. data/lib/morpheus/cli/access_token_command.rb +27 -10
  18. data/lib/morpheus/cli/apps.rb +21 -15
  19. data/lib/morpheus/cli/backup_jobs_command.rb +276 -0
  20. data/lib/morpheus/cli/backups_command.rb +271 -0
  21. data/lib/morpheus/cli/blueprints_command.rb +27 -61
  22. data/lib/morpheus/cli/boot_scripts_command.rb +1 -1
  23. data/lib/morpheus/cli/cli_command.rb +183 -45
  24. data/lib/morpheus/cli/cli_registry.rb +3 -0
  25. data/lib/morpheus/cli/clouds.rb +7 -10
  26. data/lib/morpheus/cli/clusters.rb +0 -18
  27. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +23 -20
  28. data/lib/morpheus/cli/commands/standard/man_command.rb +1 -1
  29. data/lib/morpheus/cli/credentials.rb +13 -9
  30. data/lib/morpheus/cli/deploy.rb +374 -0
  31. data/lib/morpheus/cli/deployments.rb +521 -197
  32. data/lib/morpheus/cli/deploys.rb +271 -126
  33. data/lib/morpheus/cli/doc.rb +182 -0
  34. data/lib/morpheus/cli/error_handler.rb +23 -8
  35. data/lib/morpheus/cli/errors.rb +3 -2
  36. data/lib/morpheus/cli/image_builder_command.rb +2 -2
  37. data/lib/morpheus/cli/instances.rb +136 -17
  38. data/lib/morpheus/cli/invoices_command.rb +339 -225
  39. data/lib/morpheus/cli/jobs_command.rb +2 -2
  40. data/lib/morpheus/cli/library_layouts_command.rb +1 -1
  41. data/lib/morpheus/cli/library_option_lists_command.rb +61 -125
  42. data/lib/morpheus/cli/library_option_types_command.rb +32 -37
  43. data/lib/morpheus/cli/login.rb +9 -3
  44. data/lib/morpheus/cli/mixins/accounts_helper.rb +158 -100
  45. data/lib/morpheus/cli/mixins/backups_helper.rb +115 -0
  46. data/lib/morpheus/cli/mixins/deployments_helper.rb +135 -0
  47. data/lib/morpheus/cli/mixins/library_helper.rb +32 -0
  48. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  49. data/lib/morpheus/cli/mixins/print_helper.rb +149 -84
  50. data/lib/morpheus/cli/mixins/provisioning_helper.rb +2 -2
  51. data/lib/morpheus/cli/mixins/whoami_helper.rb +19 -6
  52. data/lib/morpheus/cli/network_routers_command.rb +1 -1
  53. data/lib/morpheus/cli/option_parser.rb +48 -5
  54. data/lib/morpheus/cli/option_types.rb +46 -10
  55. data/lib/morpheus/cli/price_sets_command.rb +1 -1
  56. data/lib/morpheus/cli/projects_command.rb +7 -7
  57. data/lib/morpheus/cli/remote.rb +3 -2
  58. data/lib/morpheus/cli/roles.rb +49 -92
  59. data/lib/morpheus/cli/security_groups.rb +7 -1
  60. data/lib/morpheus/cli/service_plans_command.rb +10 -10
  61. data/lib/morpheus/cli/setup.rb +1 -1
  62. data/lib/morpheus/cli/shell.rb +7 -6
  63. data/lib/morpheus/cli/subnets_command.rb +1 -1
  64. data/lib/morpheus/cli/tasks.rb +24 -10
  65. data/lib/morpheus/cli/tenants_command.rb +133 -163
  66. data/lib/morpheus/cli/user_groups_command.rb +20 -65
  67. data/lib/morpheus/cli/user_settings_command.rb +115 -13
  68. data/lib/morpheus/cli/user_sources_command.rb +57 -24
  69. data/lib/morpheus/cli/users.rb +210 -186
  70. data/lib/morpheus/cli/version.rb +1 -1
  71. data/lib/morpheus/cli/whitelabel_settings_command.rb +29 -5
  72. data/lib/morpheus/cli/whoami.rb +113 -6
  73. data/lib/morpheus/cli/workflows.rb +11 -8
  74. data/lib/morpheus/ext/hash.rb +21 -0
  75. data/lib/morpheus/formatters.rb +7 -19
  76. data/lib/morpheus/terminal.rb +1 -0
  77. metadata +12 -3
  78. data/lib/morpheus/cli/auth_command.rb +0 -105
@@ -0,0 +1,115 @@
1
+ require 'morpheus/cli/mixins/print_helper'
2
+ # Mixin for Morpheus::Cli command classes
3
+ # Provides common methods for infrastructure management
4
+ module Morpheus::Cli::BackupsHelper
5
+
6
+ def self.included(klass)
7
+ klass.send :include, Morpheus::Cli::PrintHelper
8
+ end
9
+
10
+ def backups_interface
11
+ # @api_client.groups
12
+ raise "#{self.class} has not defined @backups_interface" if @backups_interface.nil?
13
+ @backups_interface
14
+ end
15
+
16
+ def backup_jobs_interface
17
+ # @api_client.groups
18
+ raise "#{self.class} has not defined @backup_jobs_interface" if @backup_jobs_interface.nil?
19
+ @backup_jobs_interface
20
+ end
21
+
22
+ def backup_object_key
23
+ 'backup'
24
+ end
25
+
26
+ def backup_list_key
27
+ 'backups'
28
+ end
29
+
30
+ def find_backup_by_name_or_id(val)
31
+ if val.to_s =~ /\A\d{1,}\Z/
32
+ return find_backup_by_id(val)
33
+ else
34
+ return find_backup_by_name(val)
35
+ end
36
+ end
37
+
38
+ def find_backup_by_id(id)
39
+ begin
40
+ json_response = backups_interface.get(id.to_i)
41
+ return json_response[backup_object_key]
42
+ rescue RestClient::Exception => e
43
+ if e.response && e.response.code == 404
44
+ print_red_alert "Backup not found by id '#{id}'"
45
+ else
46
+ raise e
47
+ end
48
+ end
49
+ end
50
+
51
+ def find_backup_by_name(name)
52
+ json_response = backups_interface.list({name: name.to_s})
53
+ backups = json_response[backup_list_key]
54
+ if backups.empty?
55
+ print_red_alert "Backup not found by name '#{name}'"
56
+ return nil
57
+ elsif backups.size > 1
58
+ print_red_alert "#{backups.size} backups found by name '#{name}'"
59
+ puts_error as_pretty_table(backups, [:id, :name], {color:red})
60
+ print_red_alert "Try using ID instead"
61
+ print reset,"\n"
62
+ return nil
63
+ else
64
+ return backups[0]
65
+ end
66
+ end
67
+
68
+ def backup_job_object_key
69
+ # 'backupJob'
70
+ 'job'
71
+ end
72
+
73
+ def backup_job_list_key
74
+ # 'backupJobs'
75
+ 'jobs'
76
+ end
77
+
78
+ def find_backup_job_by_name_or_id(val)
79
+ if val.to_s =~ /\A\d{1,}\Z/
80
+ return find_backup_job_by_id(val)
81
+ else
82
+ return find_backup_job_by_name(val)
83
+ end
84
+ end
85
+
86
+ def find_backup_job_by_id(id)
87
+ begin
88
+ json_response = backup_jobs_interface.get(id.to_i)
89
+ return json_response[backup_job_object_key]
90
+ rescue RestClient::Exception => e
91
+ if e.response && e.response.code == 404
92
+ print_red_alert "Backup job not found by id '#{id}'"
93
+ else
94
+ raise e
95
+ end
96
+ end
97
+ end
98
+
99
+ def find_backup_job_by_name(name)
100
+ json_response = backup_jobs_interface.list({name: name.to_s})
101
+ backup_jobs = json_response[backup_job_list_key]
102
+ if backup_jobs.empty?
103
+ print_red_alert "Backup job not found by name '#{name}'"
104
+ return nil
105
+ elsif backup_jobs.size > 1
106
+ print_red_alert "#{backup_jobs.size} backup jobs found by name '#{name}'"
107
+ puts_error as_pretty_table(backup_jobs, [:id, :name], {color:red})
108
+ print_red_alert "Try using ID instead"
109
+ print reset,"\n"
110
+ return nil
111
+ else
112
+ return backup_jobs[0]
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,135 @@
1
+ require 'morpheus/cli/mixins/print_helper'
2
+ # Mixin for Morpheus::Cli command classes
3
+ # Provides common methods for infrastructure management
4
+ module Morpheus::Cli::DeploymentsHelper
5
+
6
+ def self.included(klass)
7
+ klass.send :include, Morpheus::Cli::PrintHelper
8
+ end
9
+
10
+ ## Deployments
11
+
12
+ def deployments_interface
13
+ # @api_client.groups
14
+ raise "#{self.class} has not defined @deployments_interface" if @deployments_interface.nil?
15
+ @deployments_interface
16
+ end
17
+
18
+ def deployment_object_key
19
+ 'deployment'
20
+ end
21
+
22
+ def deployment_list_key
23
+ 'deployments'
24
+ end
25
+
26
+ def find_deployment_by_name_or_id(val)
27
+ if val.to_s =~ /\A\d{1,}\Z/
28
+ return find_deployment_by_id(val)
29
+ else
30
+ return find_deployment_by_name(val)
31
+ end
32
+ end
33
+
34
+ def find_deployment_by_id(id)
35
+ begin
36
+ json_response = deployments_interface.get(id.to_i)
37
+ return json_response[deployment_object_key]
38
+ rescue RestClient::Exception => e
39
+ if e.response && e.response.code == 404
40
+ print_red_alert "Deployment not found by id '#{id}'"
41
+ else
42
+ raise e
43
+ end
44
+ end
45
+ end
46
+
47
+ def find_deployment_by_name(name)
48
+ json_response = deployments_interface.list({name: name.to_s})
49
+ deployments = json_response[deployment_list_key]
50
+ if deployments.empty?
51
+ print_red_alert "Deployment not found by name '#{name}'"
52
+ return nil
53
+ elsif deployments.size > 1
54
+ print_red_alert "#{deployments.size} deployments found by name '#{name}'"
55
+ puts_error as_pretty_table(deployments, [:id, :name], {color:red})
56
+ print_red_alert "Try using ID instead"
57
+ print reset,"\n"
58
+ return nil
59
+ else
60
+ return deployments[0]
61
+ end
62
+ end
63
+
64
+
65
+ ## Deployment Types
66
+
67
+ # unused?
68
+ def find_deployment_type_by_name(val)
69
+ raise "find_deployment_type_by_name passed a bad name: #{val.inspect}" if val.to_s == ''
70
+ results = @deployments_interface.deployment_types(val)
71
+ result = nil
72
+ if !results['deploymentTypes'].nil? && !results['deploymentTypes'].empty?
73
+ result = results['deploymentTypes'][0]
74
+ elsif val.to_i.to_s == val
75
+ results = @deployments_interface.deployment_types(val.to_i)
76
+ result = results['deploymentType']
77
+ end
78
+ if result.nil?
79
+ print_red_alert "Deployment Type not found by '#{val}'"
80
+ return nil
81
+ end
82
+ return result
83
+ end
84
+
85
+ ## Deployment Versions
86
+
87
+ def deployment_version_object_key
88
+ # 'deploymentVersion'
89
+ 'version'
90
+ end
91
+
92
+ def deployment_version_list_key
93
+ # 'deploymentVersions'
94
+ 'versions'
95
+ end
96
+
97
+ def find_deployment_version_by_name_or_id(deployment_id, val)
98
+ if val.to_s =~ /\A\d{1,}\Z/
99
+ return find_deployment_version_by_id(deployment_id, val)
100
+ else
101
+ return find_deployment_version_by_name(deployment_id, val)
102
+ end
103
+ end
104
+
105
+ def find_deployment_version_by_id(deployment_id, id)
106
+ begin
107
+ json_response = deployments_interface.get_version(deployment_id, id.to_i)
108
+ return json_response[deployment_version_object_key]
109
+ rescue RestClient::Exception => e
110
+ if e.response && e.response.code == 404
111
+ print_red_alert "Deployment version not found by id '#{id}'"
112
+ else
113
+ raise e
114
+ end
115
+ end
116
+ end
117
+
118
+ def find_deployment_version_by_name(deployment_id, name)
119
+ json_response = deployments_interface.list_versions(deployment_id, {userVersion: name.to_s})
120
+ deployment_versions = json_response[deployment_version_list_key]
121
+ if deployment_versions.empty?
122
+ print_red_alert "Deployment version not found by version '#{name}'"
123
+ return nil
124
+ elsif deployment_versions.size > 1
125
+ print_red_alert "#{deployment_versions.size} deployment versions found by version '#{name}'"
126
+ puts_error as_pretty_table(deployment_versions, {"ID" => 'id', "VERSION" => 'userVersion'}, {color:red})
127
+ print_red_alert "Try using ID instead"
128
+ print reset,"\n"
129
+ return nil
130
+ else
131
+ return deployment_versions[0]
132
+ end
133
+ end
134
+
135
+ end
@@ -502,4 +502,36 @@ module Morpheus::Cli::LibraryHelper
502
502
  return {success:true, data: spec_template_ids}
503
503
  end
504
504
 
505
+ def find_option_type_list_by_name_or_id(val)
506
+ if val.to_s =~ /\A\d{1,}\Z/
507
+ return find_option_type_list_by_id(val)
508
+ else
509
+ return find_option_type_list_by_name(val)
510
+ end
511
+ end
512
+
513
+ def find_option_type_list_by_id(id)
514
+ begin
515
+ json_response = @option_type_lists_interface.get(id.to_i)
516
+ return json_response['optionTypeList']
517
+ rescue RestClient::Exception => e
518
+ if e.response && e.response.code == 404
519
+ print_red_alert "Option List not found by id #{id}"
520
+ exit 1
521
+ else
522
+ raise e
523
+ end
524
+ end
525
+ end
526
+
527
+ def find_option_type_list_by_name(name)
528
+ json_results = @option_type_lists_interface.list({name: name.to_s})
529
+ if json_results['optionTypeLists'].empty?
530
+ print_red_alert "Option List not found by name #{name}"
531
+ exit 1
532
+ end
533
+ option_type_list = json_results['optionTypeLists'][0]
534
+ return option_type_list
535
+ end
536
+
505
537
  end
@@ -75,7 +75,7 @@ module Morpheus::Cli::OptionSourceHelper
75
75
 
76
76
  def get_cloud_options(refresh=false, api_params={})
77
77
  if !@available_cloud_options || refresh
78
- option_results = options_interface.options_for_source('clouds', api_params)
78
+ option_results = options_interface.options_for_source('clouds', api_params.mege({'default' => 'false'}))
79
79
  @available_cloud_options = option_results['data'].collect {|it|
80
80
  {"name" => it["name"], "value" => it["value"], "id" => it["value"]}
81
81
  }
@@ -186,72 +186,49 @@ module Morpheus::Cli::PrintHelper
186
186
  payload = api_request[:payload] || api_request[:body]
187
187
  #Morpheus::Logging::DarkPrinter.puts "API payload is: (#{payload.class}) #{payload.inspect}"
188
188
  content_type = (headers && headers['Content-Type']) ? headers['Content-Type'] : 'application/x-www-form-urlencoded'
189
-
189
+ # build output, either CURL or REQUEST
190
+ output = ""
191
+ if api_request[:curl] || options[:curl]
192
+ output = format_curl_command(http_method, url, headers, payload, options)
193
+ else
194
+ output = format_api_request(http_method, url, headers, payload, options)
195
+ end
196
+ # this is an extra scrub, should remove
197
+ if options[:scrub]
198
+ output = Morpheus::Logging.scrub_message(output)
199
+ end
190
200
  # write to a file?
191
201
  if options[:outfile]
192
- output = ""
193
- if api_request[:curl] || options[:curl]
194
- output = format_curl_command(http_method, url, headers, payload, options)
195
- else
196
- # body payload
197
- output = payload
198
- if content_type == 'application/json'
199
- if payload.is_a?(String)
200
- begin
201
- payload = JSON.parse(payload)
202
- rescue => e
203
- #payload = "(unparsable) #{payload}"
204
- end
205
- end
206
- output = JSON.pretty_generate(payload)
207
- else
208
- if payload.is_a?(File)
209
- pretty_size = "#{payload.size} B"
210
- output = "File: #{payload.path} (#{pretty_size})"
211
- elsif payload.is_a?(String)
212
- output = payload
213
- else
214
- if content_type == 'application/x-www-form-urlencoded'
215
- body_str = payload.to_s
216
- begin
217
- body_str = URI.encode_www_form(payload)
218
- rescue => ex
219
- # raise ex
220
- end
221
- output = body_str
222
- else
223
- begin
224
- output = JSON.pretty_generate(payload)
225
- rescue
226
- output = payload.to_s
227
- end
228
- end
229
- end
230
- end
231
- end
232
- if options[:scrub]
233
- output = Morpheus::Logging.scrub_message(output)
234
- end
235
202
  print_result = print_to_file(output, options[:outfile], options[:overwrite])
236
- return print_result
203
+ # with_stdout_to_file(options[:outfile], options[:overwrite]) { print output }
204
+ print "#{cyan}Wrote output to file #{options[:outfile]} (#{format_bytes File.size(options[:outfile])})\n" unless options[:quiet]
205
+ #return print_result
206
+ return
237
207
  end
238
-
239
- # curl output?
208
+ # print output
240
209
  if api_request[:curl] || options[:curl]
241
210
  print "\n"
242
- puts "#{cyan}#{bold}#{dark}CURL COMMAND#{reset}"
243
- print format_curl_command(http_method, url, headers, payload, options)
211
+ print "#{cyan}#{bold}#{dark}CURL COMMAND#{reset}\n"
212
+ else
244
213
  print "\n"
245
- return
214
+ print "#{cyan}#{bold}#{dark}REQUEST#{reset}\n"
246
215
  end
216
+ print output
217
+ print reset, "\n"
218
+ print reset
219
+ return
220
+ end
247
221
 
248
- print "\n"
249
- puts "#{cyan}#{bold}#{dark}REQUEST#{reset}"
222
+ def format_api_request(http_method, url, headers, payload=nil, options={})
223
+ out = ""
224
+ # out << "\n"
225
+ # out << "#{cyan}#{bold}#{dark}REQUEST#{reset}\n"
250
226
  request_string = "#{http_method.to_s.upcase} #{url}".strip
251
- print request_string, "\n"
252
- print cyan
227
+ out << request_string + "\n"
228
+ out << cyan
253
229
  if payload
254
- print "\n"
230
+ out << "\n"
231
+ content_type = (headers && headers['Content-Type']) ? headers['Content-Type'] : 'application/x-www-form-urlencoded'
255
232
  if content_type == 'application/json'
256
233
  if payload.is_a?(String)
257
234
  begin
@@ -260,23 +237,24 @@ module Morpheus::Cli::PrintHelper
260
237
  #payload = "(unparsable) #{payload}"
261
238
  end
262
239
  end
263
- puts "#{cyan}#{bold}#{dark}JSON#{reset}"
240
+ out << "#{cyan}#{bold}#{dark}JSON#{reset}\n"
264
241
  if options[:scrub]
265
- print Morpheus::Logging.scrub_message(JSON.pretty_generate(payload))
242
+ out << Morpheus::Logging.scrub_message(JSON.pretty_generate(payload))
266
243
  else
267
- print JSON.pretty_generate(payload)
244
+ out << JSON.pretty_generate(payload)
268
245
  end
269
246
  else
270
- print "Content-Type: #{content_type}", "\n"
271
- print reset
247
+ out << "Content-Type: #{content_type}" + "\n"
248
+ out << reset
272
249
  if payload.is_a?(File)
273
- pretty_size = "#{payload.size} B"
274
- print "File: #{payload.path} (#{pretty_size})"
250
+ #pretty_size = "#{payload.size} B"
251
+ pretty_size = format_bytes(payload.size)
252
+ out << "File: #{payload.path} (#{pretty_size})"
275
253
  elsif payload.is_a?(String)
276
254
  if options[:scrub]
277
- print Morpheus::Logging.scrub_message(payload)
255
+ out << Morpheus::Logging.scrub_message(payload)
278
256
  else
279
- print payload
257
+ out << payload
280
258
  end
281
259
  else
282
260
  if content_type == 'application/x-www-form-urlencoded'
@@ -287,22 +265,24 @@ module Morpheus::Cli::PrintHelper
287
265
  # raise ex
288
266
  end
289
267
  if options[:scrub]
290
- print Morpheus::Logging.scrub_message(body_str)
268
+ out << Morpheus::Logging.scrub_message(body_str)
291
269
  else
292
- print body_str
270
+ out << body_str
293
271
  end
294
272
  else
295
273
  if options[:scrub]
296
- print Morpheus::Logging.scrub_message(payload)
274
+ out << Morpheus::Logging.scrub_message(payload)
297
275
  else
298
- print payload
276
+ out << payload
299
277
  end
300
278
  end
301
279
  end
302
280
  end
281
+ out << "\n"
303
282
  end
304
- print "\n"
305
- print reset
283
+ # out << "\n"
284
+ out << reset
285
+ return out
306
286
  end
307
287
 
308
288
  # format_curl_command generates a valid curl command for the given api request
@@ -550,10 +530,10 @@ module Morpheus::Cli::PrintHelper
550
530
  # label_width, justify = 0, "none"
551
531
  out = ""
552
532
  value = value.to_s
553
- if do_wrap && value && Morpheus::Cli::PrintHelper.terminal_width
533
+ if do_wrap && value && value.include?(" ") && Morpheus::Cli::PrintHelper.terminal_width
554
534
  value_width = Morpheus::Cli::PrintHelper.terminal_width - label_width
555
535
  if value_width > 0 && value.gsub(/\e\[(\d+)m/, '').to_s.size > value_width
556
- wrap_indent = label_width + 1 # plus 1 needs to go away
536
+ wrap_indent = label_width + 1
557
537
  value = wrap(value, value_width, wrap_indent)
558
538
  end
559
539
  end
@@ -571,7 +551,7 @@ module Morpheus::Cli::PrintHelper
571
551
  # truncate_string truncates a string and appends the suffix "..."
572
552
  # @param value [String] the string to pad
573
553
  # @param width [Integer] the length to truncate to
574
- # @param pad_char [String] the character to pad with. Default is ' '
554
+ # @param suffix [String] the character to pad right side with. Default is '...'
575
555
  def truncate_string(value, width, suffix="...")
576
556
  value = value.to_s
577
557
  # JD: hack alerty.. this sux, but it's a best effort to preserve values containing ascii coloring codes
@@ -603,6 +583,41 @@ module Morpheus::Cli::PrintHelper
603
583
  end
604
584
  end
605
585
 
586
+ # truncate_string truncates a string and appends the prefix "..."
587
+ # @param value [String] the string to pad
588
+ # @param width [Integer] the length to truncate to
589
+ # @param prefix [String] the character to pad left side with. Default is '...'
590
+ def truncate_string_right(value, width, prefix="...")
591
+ value = value.to_s
592
+ # JD: hack alerty.. this sux, but it's a best effort to preserve values containing ascii coloring codes
593
+ # it stops working when there are words separated by ascii codes, eg. two diff colors
594
+ # plus this is probably pretty slow...
595
+ uncolored_value = Term::ANSIColor.coloring? ? Term::ANSIColor.uncolored(value.to_s) : value.to_s
596
+ if uncolored_value != value
597
+ trimmed_value = nil
598
+ if uncolored_value.size > width
599
+ if prefix
600
+ trimmed_value = prefix + uncolored_value[(uncolored_value.size - width - prefix.size)..-1]
601
+ else
602
+ trimmed_value = uncolored_value[(uncolored_value.size - width)..-1]
603
+ end
604
+ return value.gsub(uncolored_value, trimmed_value)
605
+ else
606
+ return value
607
+ end
608
+ else
609
+ if value.size > width
610
+ if prefix
611
+ return prefix + value[(value.size - width - prefix.size)..-1]
612
+ else
613
+ return value[(value.size - width)..-1]
614
+ end
615
+ else
616
+ return value
617
+ end
618
+ end
619
+ end
620
+
606
621
  # justified returns a left, center, or right aligned string.
607
622
  # @param value [String] the string to pad
608
623
  # @param width [Integer] the length to truncate to
@@ -872,13 +887,7 @@ module Morpheus::Cli::PrintHelper
872
887
  out << color if color
873
888
  rows.each do |row|
874
889
  value = row[:value].to_s
875
- if do_wrap
876
- if value_width && value_width < value.size
877
- wrap_indent = label_width + 1
878
- value = wrap(value, value_width, wrap_indent)
879
- end
880
- end
881
- out << format_dt_dd(row[:label], value, label_width, justify) + "\n"
890
+ out << format_dt_dd(row[:label], value, label_width, justify, do_wrap) + "\n"
882
891
  end
883
892
  out << reset if color
884
893
  return out
@@ -1142,16 +1151,27 @@ module Morpheus::Cli::PrintHelper
1142
1151
  out
1143
1152
  end
1144
1153
 
1145
- def anded_list(items)
1154
+ def format_list(items, conjunction="and", limit=nil)
1146
1155
  items = items ? items.clone : []
1156
+ if limit
1157
+ items = items.first(limit)
1158
+ end
1147
1159
  last_item = items.pop
1148
1160
  if items.empty?
1149
1161
  return "#{last_item}"
1150
1162
  else
1151
- return items.join(", ") + " and #{last_item}"
1163
+ return items.join(", ") + (conjunction.to_s.empty? ? ", " : " #{conjunction} ") + "#{last_item}" + ((limit && limit < (items.size+1)) ? " ..." : "")
1152
1164
  end
1153
1165
  end
1154
1166
 
1167
+ def anded_list(items, limit=nil)
1168
+ format_list(items, "and", limit)
1169
+ end
1170
+
1171
+ def ored_list(items, limit=nil)
1172
+ format_list(items, "or", limit)
1173
+ end
1174
+
1155
1175
  def sleep_with_dots(sleep_seconds, dots=3, dot_chr=".")
1156
1176
  dot_interval = (sleep_seconds.to_f / dots.to_i)
1157
1177
  dots.to_i.times do |dot_index|
@@ -1161,7 +1181,7 @@ module Morpheus::Cli::PrintHelper
1161
1181
  end
1162
1182
 
1163
1183
  def print_to_file(txt, filename, overwrite=false, access_mode = 'w+')
1164
- Morpheus::Logging::DarkPrinter.puts "Writing #{txt.to_s.bytesize} bytes to file #{filename} ..." if Morpheus::Logging.debug?
1184
+ Morpheus::Logging::DarkPrinter.puts "Writing #{txt.to_s.bytesize} bytes to file #{filename}" if Morpheus::Logging.debug?
1165
1185
  outfile = nil
1166
1186
  begin
1167
1187
  full_filename = File.expand_path(filename)
@@ -1182,7 +1202,6 @@ module Morpheus::Cli::PrintHelper
1182
1202
  end
1183
1203
  outfile = File.open(full_filename, access_mode)
1184
1204
  outfile.print(txt)
1185
- print "#{cyan}Wrote #{txt.to_s.bytesize} bytes to file #{filename}\n"
1186
1205
  return 0
1187
1206
  rescue => ex
1188
1207
  # puts_error "Error writing to outfile '#{filename}'. Error: #{ex}"
@@ -1193,6 +1212,52 @@ module Morpheus::Cli::PrintHelper
1193
1212
  end
1194
1213
  end
1195
1214
 
1215
+ def with_stdout_to_file(filename, overwrite=false, access_mode = 'w+', &block)
1216
+ Morpheus::Logging::DarkPrinter.puts "Writing output to file #{filename}" if Morpheus::Logging.debug?
1217
+ previous_stdout = my_terminal.stdout
1218
+ outfile = nil
1219
+ begin
1220
+ full_filename = File.expand_path(filename)
1221
+ if File.exists?(full_filename)
1222
+ if !overwrite
1223
+ print "#{red}Output file '#{filename}' already exists.#{reset}\n"
1224
+ print "#{red}Use --overwrite to overwrite the existing file.#{reset}\n"
1225
+ return 1
1226
+ end
1227
+ end
1228
+ if Dir.exists?(full_filename)
1229
+ print "#{red}Output file '#{filename}' is invalid. It is the name of an existing directory.#{reset}\n"
1230
+ return 1
1231
+ end
1232
+ target_dir = File.dirname(full_filename)
1233
+ if !Dir.exists?(target_dir)
1234
+ FileUtils.mkdir_p(target_dir)
1235
+ end
1236
+ outfile = File.open(full_filename, access_mode)
1237
+ # outfile.print(txt)
1238
+ # ok just redirect stdout to the file
1239
+ my_terminal.set_stdout(outfile)
1240
+ result = yield
1241
+ outfile.close if outfile
1242
+ my_terminal.set_stdout(previous_stdout)
1243
+ my_terminal.stdout.flush if my_terminal.stdout
1244
+ # this does not work here.. i dunno why yet, it works in ensure though...
1245
+ # print "#{cyan}Wrote #{File.size(full_filename)} bytes to file #{filename}\n"
1246
+ if result
1247
+ return result
1248
+ else
1249
+ return 0
1250
+ end
1251
+ rescue => ex
1252
+ # puts_error "Error writing to outfile '#{filename}'. Error: #{ex}"
1253
+ print_error "#{red}Error writing to file '#{filename}'. Error: #{ex}#{reset}\n"
1254
+ return 1
1255
+ ensure
1256
+ outfile.close if outfile
1257
+ my_terminal.set_stdout(previous_stdout) if previous_stdout != my_terminal.stdout
1258
+ end
1259
+ end
1260
+
1196
1261
  def format_percent(val, sig_dig=2)
1197
1262
  if val.nil?
1198
1263
  return ""