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

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 (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