MuranoCLI 3.2.0.beta.1 → 3.2.0.beta.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/.trustme.plugin +137 -0
  4. data/.trustme.sh +217 -117
  5. data/.trustme.vim +9 -3
  6. data/Gemfile +9 -3
  7. data/MuranoCLI.gemspec +8 -5
  8. data/Rakefile +1 -0
  9. data/dockers/Dockerfile.2.2.9 +6 -3
  10. data/dockers/Dockerfile.2.3.6 +6 -3
  11. data/dockers/Dockerfile.2.4.3 +6 -3
  12. data/dockers/Dockerfile.2.5.0 +6 -3
  13. data/dockers/Dockerfile.GemRelease +10 -8
  14. data/dockers/Dockerfile.m4 +23 -5
  15. data/dockers/docker-test.sh +65 -28
  16. data/docs/completions/murano_completion-bash +751 -57
  17. data/docs/develop.rst +10 -9
  18. data/lib/MrMurano/AccountBase.rb +95 -6
  19. data/lib/MrMurano/Commander-Entry.rb +9 -4
  20. data/lib/MrMurano/Config-Migrate.rb +2 -0
  21. data/lib/MrMurano/Config.rb +94 -26
  22. data/lib/MrMurano/Content.rb +1 -1
  23. data/lib/MrMurano/Exchange.rb +77 -42
  24. data/lib/MrMurano/Gateway.rb +1 -1
  25. data/lib/MrMurano/HttpAuthed.rb +20 -7
  26. data/lib/MrMurano/Logs.rb +10 -1
  27. data/lib/MrMurano/ProjectFile.rb +1 -1
  28. data/lib/MrMurano/ReCommander.rb +129 -73
  29. data/lib/MrMurano/Solution-ServiceConfig.rb +18 -11
  30. data/lib/MrMurano/Solution-Services.rb +78 -50
  31. data/lib/MrMurano/Solution-Users.rb +1 -1
  32. data/lib/MrMurano/Solution.rb +13 -63
  33. data/lib/MrMurano/SyncUpDown-Core.rb +185 -77
  34. data/lib/MrMurano/SyncUpDown-Item.rb +29 -4
  35. data/lib/MrMurano/SyncUpDown.rb +11 -11
  36. data/lib/MrMurano/Webservice-Cors.rb +1 -1
  37. data/lib/MrMurano/Webservice-Endpoint.rb +28 -17
  38. data/lib/MrMurano/Webservice-File.rb +103 -43
  39. data/lib/MrMurano/commands/domain.rb +1 -0
  40. data/lib/MrMurano/commands/element.rb +585 -0
  41. data/lib/MrMurano/commands/exchange.rb +211 -204
  42. data/lib/MrMurano/commands/gb.rb +1 -0
  43. data/lib/MrMurano/commands/globals.rb +17 -7
  44. data/lib/MrMurano/commands/init.rb +115 -101
  45. data/lib/MrMurano/commands/keystore.rb +1 -1
  46. data/lib/MrMurano/commands/logs.rb +2 -1
  47. data/lib/MrMurano/commands/postgresql.rb +17 -7
  48. data/lib/MrMurano/commands/service.rb +572 -0
  49. data/lib/MrMurano/commands/show.rb +7 -3
  50. data/lib/MrMurano/commands/solution.rb +2 -1
  51. data/lib/MrMurano/commands/solution_picker.rb +31 -15
  52. data/lib/MrMurano/commands/status.rb +205 -169
  53. data/lib/MrMurano/commands/sync.rb +70 -38
  54. data/lib/MrMurano/commands/token.rb +59 -14
  55. data/lib/MrMurano/commands/usage.rb +1 -0
  56. data/lib/MrMurano/commands.rb +2 -0
  57. data/lib/MrMurano/hash.rb +91 -0
  58. data/lib/MrMurano/http.rb +55 -6
  59. data/lib/MrMurano/makePretty.rb +47 -0
  60. data/lib/MrMurano/optparse.rb +60 -45
  61. data/lib/MrMurano/variegated/TruthyFalsey.rb +48 -0
  62. data/lib/MrMurano/variegated/ruby_dig.rb +64 -0
  63. data/lib/MrMurano/verbosing.rb +113 -3
  64. data/lib/MrMurano/version.rb +1 -1
  65. data/spec/Account_spec.rb +34 -20
  66. data/spec/Business_spec.rb +12 -9
  67. data/spec/Config_spec.rb +7 -1
  68. data/spec/Content_spec.rb +17 -1
  69. data/spec/GatewayBase_spec.rb +5 -2
  70. data/spec/GatewayDevice_spec.rb +4 -2
  71. data/spec/GatewayResource_spec.rb +4 -1
  72. data/spec/GatewaySettings_spec.rb +4 -1
  73. data/spec/HttpAuthed_spec.rb +73 -0
  74. data/spec/Http_spec.rb +32 -35
  75. data/spec/ProjectFile_spec.rb +1 -1
  76. data/spec/Solution-ServiceConfig_spec.rb +4 -1
  77. data/spec/Solution-ServiceEventHandler_spec.rb +6 -3
  78. data/spec/Solution-ServiceModules_spec.rb +4 -1
  79. data/spec/Solution-UsersRoles_spec.rb +4 -1
  80. data/spec/Solution_spec.rb +4 -1
  81. data/spec/SyncUpDown_spec.rb +1 -1
  82. data/spec/Webservice-Cors_spec.rb +4 -1
  83. data/spec/Webservice-Endpoint_spec.rb +9 -6
  84. data/spec/Webservice-File_spec.rb +17 -4
  85. data/spec/Webservice-Setting_spec.rb +6 -2
  86. data/spec/_workspace.rb +2 -0
  87. data/spec/cmd_common.rb +42 -13
  88. data/spec/cmd_content_spec.rb +17 -7
  89. data/spec/cmd_device_spec.rb +1 -1
  90. data/spec/cmd_domain_spec.rb +2 -2
  91. data/spec/cmd_element_spec.rb +400 -0
  92. data/spec/cmd_exchange_spec.rb +2 -2
  93. data/spec/cmd_init_spec.rb +59 -25
  94. data/spec/cmd_keystore_spec.rb +6 -3
  95. data/spec/cmd_link_spec.rb +10 -5
  96. data/spec/cmd_logs_spec.rb +1 -1
  97. data/spec/cmd_setting_application_spec.rb +18 -15
  98. data/spec/cmd_setting_product_spec.rb +7 -7
  99. data/spec/cmd_status_spec.rb +27 -17
  100. data/spec/cmd_syncdown_application_spec.rb +30 -3
  101. data/spec/cmd_syncdown_both_spec.rb +72 -18
  102. data/spec/cmd_syncup_spec.rb +71 -5
  103. data/spec/cmd_token_spec.rb +2 -2
  104. data/spec/cmd_usage_spec.rb +2 -2
  105. data/spec/dry_run_formatter.rb +27 -0
  106. data/spec/fixtures/dumped_config +8 -0
  107. data/spec/fixtures/exchange_element/element-show.json +1 -0
  108. data/spec/fixtures/exchange_element/swagger-mur-6407__10k.yaml +282 -0
  109. data/spec/fixtures/exchange_element/swagger-mur-6407__20k.yaml +588 -0
  110. data/spec/variegated_TruthyFalsey_spec.rb +29 -0
  111. metadata +51 -25
@@ -6,6 +6,7 @@
6
6
  # Unauthorized copying of this file is strictly prohibited.
7
7
 
8
8
  require 'highline'
9
+ require 'MrMurano/makePretty'
9
10
  require 'MrMurano/verbosing'
10
11
  require 'MrMurano/Exchange'
11
12
  require 'MrMurano/ReCommander'
@@ -14,261 +15,267 @@ require 'MrMurano/commands/business'
14
15
  # *** Business commands: Exchange Elements
15
16
  # ----------------------------------------
16
17
 
17
- command :exchange do |c|
18
- c.syntax = %(murano exchange)
19
- c.summary = %(About IOT Exchange)
20
- c.description = %(
21
- Commands for working with IOT Exchange.
22
- ).strip
23
- c.project_not_required = true
24
- c.subcmdgrouphelp = true
25
-
26
- c.action do |_args, _options|
27
- ::Commander::UI.enable_paging unless $cfg['tool.no-page']
28
- say MrMurano::SubCmdGroupHelp.new(c).get_help
18
+ class ExchangeCmd
19
+ include MrMurano::Verbose
20
+
21
+ def command_exchange_help(cmd)
22
+ cmd.syntax = %(murano exchange)
23
+ cmd.summary = %(IoT Marketplace Exchange commands)
24
+ cmd.description = %(
25
+ Commands for working with IoT Marketplace.
26
+ ).strip
27
+ cmd.project_not_required = true
28
+ cmd.subcmdgrouphelp = true
29
+
30
+ cmd.action do |_args, _options|
31
+ ::Commander::UI.enable_paging unless $cfg['tool.no-page']
32
+ say MrMurano::SubCmdGroupHelp.new(cmd).get_help
33
+ end
29
34
  end
30
- end
31
35
 
32
- command 'exchange list' do |c|
33
- c.syntax = %(murano exchange list [--options] [<name-or-ID>])
34
- c.summary = %(List Exchange Elements)
35
- c.description = %(
36
+ def command_exchange_list(cmd)
37
+ cmd.syntax = %(murano exchange list [--options] [<name-or-ID>])
38
+ cmd.summary = %(List Exchange Elements)
39
+ cmd.description = %(
36
40
  List Exchange Elements, either all of them, or those that are purchased or available.
37
41
 
38
42
  Each Exchange Element is identified by an Element ID and a name.
39
43
 
40
44
  Element status:
41
45
 
42
- - added An Element that has been added to and enabled for your Business
46
+ - added An Element that has been added to and enabled for your Business
43
47
 
44
- - available An Element that can be added to and enabled for your Business
48
+ - available An Element that can be added to and enabled for your Business
45
49
 
46
- - available* An Element that you can use if you upgrade your Business tier
50
+ - available* An Element that you can use if you upgrade your Business tier
47
51
 
48
- ).strip
49
- c.project_not_required = true
52
+ ).strip
53
+ cmd.project_not_required = true
50
54
 
51
- cmd_table_output_add_options(c)
55
+ cmd_table_output_add_options(cmd)
52
56
 
53
- c.option '--[no-]added', 'Only show Elements that have been added to the Application'
54
- c.option '--[no-]full', 'Show all fields'
55
- c.option '--[no-]other', 'Show other fields: like type, tiers, tags, and action, and apiServiceName'
57
+ cmd.option '--[no-]added', 'Only show Elements that have been added to the Business'
58
+ cmd.option '--[no-]elem-type', 'Show element "type"'
59
+ cmd.option '--[no-]full', 'Show all fields'
60
+ cmd.option '--[no-]other', 'Show other fields: like type, tiers, tags, and action, and apiServiceName'
56
61
 
57
- # Add --id and --name options.
58
- cmd_options_add_id_and_name(c)
62
+ # Add --id and --name options.
63
+ cmd_options_add_id_and_name(cmd)
59
64
 
60
- c.action do |args, options|
61
- c.verify_arg_count!(args, 1)
62
- cmd_defaults_id_and_name(options)
65
+ cmd.action do |args, options|
66
+ cmd.verify_arg_count!(args, 1)
67
+ cmd_defaults_id_and_name(options)
63
68
 
64
- xchg = MrMurano::Exchange.new
65
- xchg.must_business_id!
69
+ xchg = MrMurano::Exchange.new
70
+ xchg.must_business_id!
66
71
 
67
- elems, available, purchased = find_elements(xchg, options, args[0])
68
- if options.added.nil?
69
- show = elems
70
- elsif options.added
71
- show = purchased
72
- else
73
- show = available
74
- end
72
+ show = filter_elements(xchg, options, args[0])
73
+ sorted = sort_elements(show)
75
74
 
76
- headers, pruned = cmd_exchange_header_and_elems(show, options)
75
+ headers, pruned = cmd_exchange_header_and_elems(sorted, options)
77
76
 
78
- io = File.open(options.output, 'w') if options.output
79
- xchg.outf(pruned, io) do |item, ios|
80
- if options.idonly
81
- ios.puts item
77
+ io = File.open(options.output, 'w') if options.output
78
+ xchg.outf(pruned, io) do |item, ios|
79
+ if options.idonly
80
+ ios.puts item
81
+ else
82
+ ios.puts "Found #{pruned.length} elements."
83
+ xchg.tabularize(
84
+ {
85
+ headers: headers.map(&:to_s),
86
+ rows: item,
87
+ },
88
+ ios,
89
+ )
90
+ end
91
+ end
92
+ io.close unless io.nil?
93
+ end
94
+ end
95
+
96
+ def find_elements(xchg, options, term, skip_purchased: false)
97
+ filter_id = nil
98
+ filter_name = nil
99
+ filter_fuzzy = nil
100
+ if term
101
+ if options.id
102
+ filter_id = term
103
+ elsif options.name
104
+ filter_name = term
82
105
  else
83
- ios.puts "Found #{pruned.length} elements."
84
- xchg.tabularize(
85
- {
86
- headers: headers.map(&:to_s),
87
- rows: item,
88
- },
89
- ios,
90
- )
106
+ filter_fuzzy = term
91
107
  end
92
108
  end
93
- io.close unless io.nil?
109
+ xchg.elements(
110
+ filter_id: filter_id,
111
+ filter_name: filter_name,
112
+ filter_fuzzy: filter_fuzzy,
113
+ skip_purchased: skip_purchased,
114
+ )
94
115
  end
95
- end
96
- alias_command 'exchange list available', 'exchange list', '--no-added'
97
- alias_command 'exchange list purchased', 'exchange list', '--added'
98
-
99
- def find_elements(xchg, options, term)
100
- filter_id = nil
101
- filter_name = nil
102
- filter_fuzzy = nil
103
- if term
104
- if options.id
105
- filter_id = term
106
- elsif options.name
107
- filter_name = term
116
+
117
+ def filter_elements(xchg, options, term)
118
+ elems, available, purchased = find_elements(xchg, options, term)
119
+ if options.added.nil?
120
+ elems
121
+ elsif options.added
122
+ purchased
108
123
  else
109
- filter_fuzzy = term
124
+ available
110
125
  end
111
126
  end
112
- xchg.elements(filter_id: filter_id, filter_name: filter_name, filter_fuzzy: filter_fuzzy)
113
- end
114
127
 
115
- def cmd_exchange_header_and_elems(elems, options)
116
- # MAYBE/2017-08-31: If you `-c outformat=json`, each Element is a
117
- # list of values, rather than a dictionary. Wouldn't the JSON be
118
- # easier to consume if each Element was a dict, rather than list?
119
- if options.idonly
120
- headers = %i[elementId]
121
- elems = elems.map(&:elementId)
122
- elsif options.brief
123
- headers = %i[elementId name]
124
- headers += [:status] unless options.added
125
- #elems = elems.map { |elem| [elem.elementId, elem.name] }
126
- elems = elems.map { |elem| headers.map { |key| elem.send(key) } }
127
- elsif options.full
128
- headers = %i[elementId name status type apiServiceName tiers tags actions markdown]
129
- all_hdrs = (elems[0] && elems[0].meta.keys) || []
130
- all_hdrs.each do |chk|
131
- headers.push(chk) unless headers.include?(chk)
128
+ STATUSABLE_RANK = %i[added available upgrade].freeze
129
+
130
+ def sort_elements(elems)
131
+ elems.sort do |lhs, rhs|
132
+ if lhs.statusable != rhs.statusable
133
+ next(
134
+ STATUSABLE_RANK.index(lhs.statusable) <=> STATUSABLE_RANK.index(rhs.statusable)
135
+ )
136
+ end
137
+ lhs.name <=> rhs.name
132
138
  end
133
- #elems = elems.map { |elem| headers.map { |key| elem.meta[key] } }
134
- elems = elems.map { |elem| headers.map { |key| elem.send(key) || '' } }
135
- elsif options.other
136
- # NOTE: Showing columns not displayed when --other not specified,
137
- # except not showing :markdown, ever.
138
- headers = %i[elementId type apiServiceName tiers tags actions]
139
- #headers = %i[elementId type apiServiceName tiers tags actions markdown]
140
- #elems = elems.map { |elem| headers.map { |key| elem.send(key) } }
141
- elems = elems.map do |elem|
139
+ end
140
+
141
+ def cmd_exchange_header_and_elems_others(elems)
142
+ elems.map do |elem|
143
+ elem_actions = elem.actions.nil? && [] || elem.actions.map do |action|
144
+ action.map { |key, val| "#{key}: #{val}" }.join("\n")
145
+ end
142
146
  [
143
147
  elem.elementId,
144
148
  elem.type,
145
149
  elem.apiServiceName,
146
- #elem.tiers,
147
- #elem.tiers.join(' | '),
148
150
  elem.tiers.join("\n"),
149
- #elem.tags,
150
- #elem.tags.join(' | '),
151
151
  elem.tags.join("\n"),
152
- #elem.actions,
153
- elem.actions.map { |actn| actn.map { |key, val| "#{key}: #{val}" }.join("\n") }.join("\n"),
154
- #elem.markdown.gsub("\n", '\\n'),
152
+ elem_actions.join("\n"),
155
153
  ]
156
154
  end
157
- else
158
- # 2017-08-28: There are 9 keys, and one of them -- :markdown -- is a
159
- # lot of text, so rather than, e.g., elems[0].meta.keys, be selective.
160
- headers = %i[elementId name]
161
- headers += [:status] unless options.added
162
- headers += [:description]
163
- if $stdout.tty?
164
- # Calculate how much room (how many characters) are left for the
165
- # description column.
166
- width_taken = 0
167
- # rubocop:disable Performance/FixedSize
168
- # "Do not compute the size of statically sized objects."
169
- width_taken += '| '.length
170
- # Calculate the width of each column except the last (:description).
171
- headers[0..-2].each do |key|
172
- elem_with_max = elems.max { |a, b| a.send(key).length <=> b.send(key).length }
173
- width_taken += elem_with_max.send(key).length unless elem_with_max.nil?
174
- width_taken += ' | '.length
155
+ end
156
+
157
+ def cmd_exchange_header_and_elems_default(headers, elems, width_avail)
158
+ elems.map do |elem|
159
+ headers.map do |key|
160
+ full = elem.send(key)
161
+ next(full) if width_avail.nil? || key != :description || full.empty?
162
+ MrMurano::Pretties.split_text_on_whitespace(full, width_avail)
163
+ end
164
+ end
165
+ end
166
+
167
+ def cmd_exchange_header_and_elems(elems, options)
168
+ # MAYBE/2017-08-31: If you `-c outformat=json`, each Element is a
169
+ # list of values, rather than a dictionary. Wouldn't the JSON be
170
+ # easier to consume if each Element was a dict, rather than list?
171
+ if options.idonly
172
+ headers = %i[elementId]
173
+ elems = elems.map(&:elementId)
174
+ elsif options.brief
175
+ headers = %i[elementId name]
176
+ headers += [:status] unless options.added
177
+ elems = elems.map { |elem| headers.map { |key| elem.send(key) } }
178
+ elsif options.full
179
+ headers = %i[elementId name status type apiServiceName tiers tags actions markdown]
180
+ all_hdrs = (elems[0] && elems[0].meta.keys) || []
181
+ all_hdrs.each do |chk|
182
+ headers.push(chk) unless headers.include?(chk)
175
183
  end
176
- width_taken += ' | '.length
177
- term_width, _rows = HighLine::SystemExtensions.terminal_size
178
- width_avail = term_width - width_taken
179
- # MAGIC_NUMBER: Tweak/change this if you want. 20 char min feels
180
- # about right: don't wrap if column would be narrow or negative.
181
- width_avail = nil if width_avail < 20
184
+ elems = elems.map { |elem| headers.map { |key| elem.send(key) || '' } }
185
+ elsif options.other
186
+ # NOTE: Showing columns not displayed when --other not specified,
187
+ # except not showing :markdown, ever.
188
+ headers = %i[elementId type apiServiceName tiers tags actions]
189
+ elems = cmd_exchange_header_and_elems_others(elems)
182
190
  else
183
- width_avail = nil
191
+ # 2017-08-28: There are 9 keys, and one of them -- :markdown -- is a
192
+ # lot of text, so rather than, e.g., elems[0].meta.keys, be selective.
193
+ headers = %i[elementId name]
194
+ headers += [:type] if options.elem_type
195
+ headers += [:status] unless options.added
196
+ headers += [:description]
197
+ width_avail = MrMurano::Pretties.width_last_column(headers, elems)
198
+ elems = cmd_exchange_header_and_elems_default(headers, elems, width_avail)
184
199
  end
185
- #elems = elems.map { |elem| headers.map { |key| elem.send(key) } }
186
- elems = elems.map do |elem|
187
- headers.map do |key|
188
- if !width_avail.nil? && key == :description
189
- #elem.meta[key].scan(/.{1,#{width_avail}}/).join("\n")
190
- full = elem.send(key)
191
- parts = []
192
- until full.empty?
193
- # Split the description on a space before the max width.
194
- # FIXME/2017-08-28: Need to test really long desc with no space.
195
- part = full[0..width_avail]
196
- full = full[(width_avail + 1)..-1] || ''
197
- leftover = ''
198
- part, _space, leftover = part.rpartition(' ') unless full.empty?
199
- if part.empty?
200
- part = leftover.to_s
201
- leftover = ''
202
- else
203
- full = leftover.to_s + full
204
- full = full
205
- end
206
- parts.push(part.strip)
207
- end
208
- parts.join("\n")
209
- else
210
- elem.send(key)
211
- end
200
+ headers.delete(:type) if options.elem_type == false
201
+ [headers, elems]
202
+ end
203
+
204
+ def command_exchange_purchase(cmd)
205
+ cmd.syntax = %(murano exchange purchase [--options] <name-or-ID>)
206
+ cmd.summary = %(Add an Exchange Element to your Business)
207
+ cmd.description = %(
208
+ Add an Exchange Element to your Business.
209
+ ).strip
210
+ # It feels a little weird to not require a project, but all
211
+ # we need is the Business ID; this action does not apply to
212
+ # solutions.
213
+ cmd.project_not_required = true
214
+
215
+ # Add --id and --name options.
216
+ cmd_options_add_id_and_name(cmd)
217
+
218
+ cmd.action do |args, options|
219
+ cmd.verify_arg_count!(args, 1, ['Missing Element name or ID'])
220
+ cmd_defaults_id_and_name(options)
221
+
222
+ xchg = MrMurano::Exchange.new
223
+ xchg.must_business_id!
224
+
225
+ # If the user specifies filter_id, we could try to fetch that Element
226
+ # directly (e.g., by calling exchange/<bizId>/element/<elemId>),
227
+ # but the response doesn't specify if the Element is purchased or not.
228
+ # So we grab everything from /element/ and /purchase/.
229
+
230
+ elems, _available, purchased = find_elements(xchg, options, args[0])
231
+
232
+ elems_must_found_one!(elems, xchg)
233
+ if purchased.length == 1
234
+ # I.e., elems.status == :added
235
+ xchg.warning(
236
+ 'The specified element has already been purchased: ' \
237
+ "#{xchg.fancy_ticks(purchased[0].name)} (#{purchased[0].elementId})"
238
+ )
239
+ exit 2
240
+ elsif elems.first.status == :upgrade
241
+ xchg.warning('Please upgrade your Business to add this Element. Visit:')
242
+ xchg.warning(' https://www.exosite.io/business/settings/upgrade')
243
+ exit 2
212
244
  end
245
+
246
+ xchg.purchase(elems.first.elementId)
213
247
  end
214
248
  end
215
- [headers, elems]
216
- end
217
249
 
218
- command 'exchange purchase' do |c|
219
- c.syntax = %(murano exchange purchase [--options] <name-or-ID>)
220
- c.summary = %(Add an Exchange Element to your Business)
221
- c.description = %(
222
- Add an Exchange Element to your Business.
223
- ).strip
224
- # It feels a little weird to not require a project, but all
225
- # we need is the Business ID; this action does not apply to
226
- # solutions.
227
- c.project_not_required = true
228
-
229
- # Add --id and --name options.
230
- cmd_options_add_id_and_name(c)
231
-
232
- c.action do |args, options|
233
- c.verify_arg_count!(args, 1, ['Missing Element name or ID'])
234
- cmd_defaults_id_and_name(options)
235
-
236
- xchg = MrMurano::Exchange.new
237
- xchg.must_business_id!
238
-
239
- # If the user specifies filter_id, we could try to fetch that Element
240
- # directly (e.g., by calling exchange/<bizId>/element/<elemId>),
241
- # but the response doesn't specify if the Element is purchased or not.
242
- # So we grab everything from /element/ and /purchase/.
243
-
244
- elems, _available, purchased = find_elements(xchg, options, args[0])
250
+ def elems_must_found_one!(elems, xchg)
245
251
  if elems.length > 1
246
- idents = elems.map { |elem| "#{xchg.fancy_ticks(elem.name)} (#{elem.elementId})" }
247
- idents[-1] = 'and ' + idents[-1]
252
+ idents = elems.map { |elem| "#{elem.elementId}: #{xchg.fancy_ticks(elem.name)}" }
248
253
  xchg.warning(
249
- 'Please be more specific: More than one matching element was found: ' \
250
- "#{idents.join(', ')}"
254
+ "Please be more specific: More than one matching element was found:\n " \
255
+ "#{idents.join("\n ")}"
251
256
  )
252
257
  exit 2
253
258
  elsif elems.empty?
254
259
  xchg.warning('No matching element was found.')
255
260
  exit 2
256
- elsif purchased.length == 1
257
- # I.e., elems.status == :added
258
- xchg.warning(
259
- 'The specified element has already been purchased: ' \
260
- "#{xchg.fancy_ticks(purchased[0].name)} (#{purchased[0].elementId})"
261
- )
262
- exit 2
263
- elsif elems.first.status == :upgrade
264
- xchg.warning('Please upgrade your Business to add this Element. Visit:')
265
- xchg.warning(' https://www.exosite.io/business/settings/upgrade')
266
- exit 2
267
261
  end
268
-
269
- xchg.purchase(elems.first.elementId)
262
+ elems.first
270
263
  end
271
264
  end
272
- alias_command 'exchange add', 'exchange purchase'
273
- alias_command 'exchange buy', 'exchange purchase'
265
+
266
+ def wire_cmd_exchange
267
+ exchange_cmd = ExchangeCmd.new
268
+
269
+ command(:exchange) { |cmd| exchange_cmd.command_exchange_help(cmd) }
270
+
271
+ command('exchange list') { |cmd| exchange_cmd.command_exchange_list(cmd) }
272
+ alias_command 'exchange list available', 'exchange list', '--no-added'
273
+ alias_command 'exchange list purchased', 'exchange list', '--added'
274
+
275
+ command('exchange purchase') { |cmd| exchange_cmd.command_exchange_purchase(cmd) }
276
+ alias_command 'exchange add', 'exchange purchase'
277
+ alias_command 'exchange buy', 'exchange purchase'
278
+ end
279
+
280
+ wire_cmd_exchange
274
281
 
@@ -6,6 +6,7 @@
6
6
  # Unauthorized copying of this file is strictly prohibited.
7
7
 
8
8
  require 'pp'
9
+ require 'MrMurano/commands/solution_picker'
9
10
 
10
11
  # You don't need this.
11
12
  # To use this:
@@ -7,6 +7,7 @@
7
7
 
8
8
  require 'MrMurano/verbosing'
9
9
  require 'MrMurano/Config'
10
+ require 'MrMurano/variegated/TruthyFalsey'
10
11
 
11
12
  global_option('--[no-]color', %(Disable fancy output)) do |value|
12
13
  HighLine.use_color = value
@@ -14,16 +15,20 @@ global_option('--[no-]color', %(Disable fancy output)) do |value|
14
15
  end
15
16
 
16
17
  global_option('-c', '--config KEY=VALUE', %(Set a single config key)) do |param|
18
+ # a=b :> ['a', 'b']
19
+ # a= :> ['a', '']
20
+ # a :> ['a', nil]
17
21
  key, value = param.split('=', 2)
18
- # a=b :> ["a", "b"]
19
- # a= :> ["a", ""]
20
- # a :> ["a"]
21
22
  raise "Bad config '#{param}'" if key.nil?
22
- if value.nil?
23
- $cfg[key] = 'true'
24
- else
25
- $cfg[key] = value
23
+ section, ikey = key.split('.', 2)
24
+ if ikey.nil?
25
+ ikey = section
26
+ section = 'tool'
27
+ key = "#{section}.#{ikey}"
26
28
  end
29
+ # If existing config setting is a boolean, coerce value as such,
30
+ # rather than setting a string, e.g., false, not "false".
31
+ $cfg[key] = TruthyFalsey.coerce_boolean(value, $cfg[key])
27
32
  end
28
33
 
29
34
  global_option('-C', '--configfile FILE', %(Load additional configuration file)) do |file|
@@ -89,6 +94,11 @@ global_option('--debug', %(Show debug messages)) do
89
94
  $cfg['tool.debug'] = true
90
95
  end
91
96
 
97
+ # 2018-04-24: (lb): Let's *not* advertise this, shall we?
98
+ # global_option('--developer', %(Enable developer mode)) do
99
+ # $cfg['tool.developer'] = true
100
+ # end
101
+
92
102
  global_option('--sid VALUE', %(Override application or product ID)) do |value|
93
103
  $cfg['application.id'] = value
94
104
  $cfg['product.id'] = value