MuranoCLI 3.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +50 -27
  3. data/.trustme.vim +12 -8
  4. data/bin/murano +23 -8
  5. data/docs/basic_example.rst +1 -1
  6. data/docs/completions/murano_completion-bash +88 -0
  7. data/lib/MrMurano/Account.rb +3 -3
  8. data/lib/MrMurano/Business.rb +6 -6
  9. data/lib/MrMurano/Config-Migrate.rb +1 -3
  10. data/lib/MrMurano/Config.rb +16 -8
  11. data/lib/MrMurano/Content.rb +56 -45
  12. data/lib/MrMurano/Gateway.rb +62 -21
  13. data/lib/MrMurano/Mock.rb +27 -19
  14. data/lib/MrMurano/Passwords.rb +7 -7
  15. data/lib/MrMurano/ReCommander.rb +171 -28
  16. data/lib/MrMurano/Setting.rb +38 -40
  17. data/lib/MrMurano/Solution-ServiceConfig.rb +2 -1
  18. data/lib/MrMurano/Solution-Services.rb +196 -61
  19. data/lib/MrMurano/Solution-Users.rb +7 -7
  20. data/lib/MrMurano/Solution.rb +22 -8
  21. data/lib/MrMurano/SolutionId.rb +10 -4
  22. data/lib/MrMurano/SubCmdGroupContext.rb +14 -5
  23. data/lib/MrMurano/SyncAllowed.rb +42 -0
  24. data/lib/MrMurano/SyncUpDown.rb +122 -65
  25. data/lib/MrMurano/Webservice-Cors.rb +9 -3
  26. data/lib/MrMurano/Webservice-Endpoint.rb +39 -33
  27. data/lib/MrMurano/Webservice-File.rb +35 -24
  28. data/lib/MrMurano/commands/business.rb +18 -18
  29. data/lib/MrMurano/commands/content.rb +3 -2
  30. data/lib/MrMurano/commands/devices.rb +137 -22
  31. data/lib/MrMurano/commands/globals.rb +8 -2
  32. data/lib/MrMurano/commands/keystore.rb +3 -2
  33. data/lib/MrMurano/commands/link.rb +13 -13
  34. data/lib/MrMurano/commands/login.rb +3 -2
  35. data/lib/MrMurano/commands/mock.rb +4 -3
  36. data/lib/MrMurano/commands/password.rb +4 -2
  37. data/lib/MrMurano/commands/postgresql.rb +5 -3
  38. data/lib/MrMurano/commands/settings.rb +78 -62
  39. data/lib/MrMurano/commands/show.rb +79 -74
  40. data/lib/MrMurano/commands/solution.rb +6 -4
  41. data/lib/MrMurano/commands/solution_picker.rb +5 -4
  42. data/lib/MrMurano/commands/status.rb +15 -4
  43. data/lib/MrMurano/commands/timeseries.rb +3 -2
  44. data/lib/MrMurano/commands/tsdb.rb +3 -2
  45. data/lib/MrMurano/hash.rb +6 -6
  46. data/lib/MrMurano/http.rb +66 -67
  47. data/lib/MrMurano/makePretty.rb +18 -12
  48. data/lib/MrMurano/progress.rb +9 -2
  49. data/lib/MrMurano/verbosing.rb +14 -2
  50. data/lib/MrMurano/version.rb +2 -2
  51. data/spec/GatewayDevice_spec.rb +190 -149
  52. data/spec/Mock_spec.rb +3 -3
  53. data/spec/Solution-ServiceEventHandler_spec.rb +170 -137
  54. data/spec/SyncUpDown_spec.rb +205 -191
  55. metadata +3 -2
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.17 /coding: utf-8
1
+ # Last Modified: 2017.08.23 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -32,12 +32,6 @@ module MrMurano
32
32
  return unless @path.exist?
33
33
  @path.chmod(0o600)
34
34
  @path.open('rb') do |io|
35
- # 2017-07-01: Rubocop suggests using safe_load.
36
- # https://ruby-doc.org/stdlib-2.1.0/libdoc/psych/rdoc/Psych.html#method-c-load
37
- # https://ruby-doc.org/stdlib-2.1.0/libdoc/psych/rdoc/Psych.html#method-c-safe_load
38
- # 2017-07-05: Oh, I don't think safe_load exists in Ruby 2 or 2.2...
39
- #@data = YAML.safe_load(io)
40
- # rubocop:disable Security/YAMLLoad
41
35
  @data = YAML.load(io)
42
36
  end
43
37
  end
@@ -87,6 +81,12 @@ module MrMurano
87
81
  return unless @data.is_a?(Hash)
88
82
  hd = @data[host]
89
83
  return unless !hd.nil? && hd.is_a?(Hash)
84
+ if $cfg['tool.dry']
85
+ MrMurano::Verbose.whirly_interject do
86
+ say(%(--dry: Not removing password for #{fancy_ticks("#{user}@#{host}")}))
87
+ end
88
+ return
89
+ end
90
90
  @data[host].delete(user) if hd.key?(user)
91
91
  end
92
92
 
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.16 /coding: utf-8
1
+ # Last Modified: 2017.08.23 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -7,7 +7,6 @@
7
7
 
8
8
  require 'optparse'
9
9
  require 'MrMurano/optparse'
10
-
11
10
  require 'MrMurano/verbosing'
12
11
 
13
12
  module MrMurano
@@ -50,6 +49,8 @@ module Commander
50
49
  # A command sets prompt_if_logged_off if it
51
50
  # is okay to ask the user for their password.
52
51
  attr_accessor :prompt_if_logged_off
52
+ # A command sets subcmdgrouphelp if it's help.
53
+ attr_accessor :subcmdgrouphelp
53
54
 
54
55
  def verify_arg_count!(args, max_args=0, mandatory=[])
55
56
  if !max_args.nil? && max_args.zero?
@@ -126,38 +127,180 @@ module Commander
126
127
 
127
128
  alias old_parse_global_options parse_global_options
128
129
  def parse_global_options
130
+ # User can specify, e.g., status.options, to set options for specific commands.
129
131
  defopts = ($cfg["#{active_command.name}.options"] || '').split
130
132
  @args.push(*defopts)
131
- begin
132
- old_parse_global_options
133
- rescue OptionParser::MissingArgument => err
134
- if err.message.start_with?('missing argument:')
135
- puts err.message
136
- else
137
- MrMurano::Verbose.error(
138
- "There was a problem interpreting the options: ‘#{err.message}’"
139
- )
133
+ help_hack
134
+ parse_global_options_real
135
+ end
136
+
137
+ def parse_global_options_real
138
+ old_parse_global_options
139
+ rescue OptionParser::MissingArgument => err
140
+ if err.message.start_with?('missing argument:')
141
+ puts err.message
142
+ else
143
+ err_msg = MrMurano::Verbose.fancy_ticks(err.message)
144
+ MrMurano::Verbose.error(
145
+ "There was a problem interpreting the options: #{err_msg}"
146
+ )
147
+ end
148
+ exit 2
149
+ end
150
+
151
+ # 2017-08-22: Commander's help infrastructure is either really weak,
152
+ # or we did something elsewhere that seriously cripples it. In any
153
+ # case, this fixes all its quirks.
154
+ def help_hack
155
+ do_help = fix_args
156
+ show_alias_help_maybe! if do_help
157
+ end
158
+
159
+ def help_opts
160
+ %w[-h --help help].freeze
161
+ end
162
+
163
+ def vers_opts
164
+ %w[-v --version].freeze
165
+ end
166
+
167
+ def fix_args
168
+ # If `murano --help` is specified, let rb-commander handle it.
169
+ # But if `murano command --help` is specified, don't let cmdr
170
+ # handle it, otherwise it just shows the command description,
171
+ # but not any of the subcommands (our SubCmdGroupContext code).
172
+ # Note: not checking help_opts here, which includes 'help', because
173
+ # 'help' might really be a command argument (e.g., a solution name).
174
+ # Note: `murano -v`'s active_command is 'help'.
175
+ do_help = (@args & %w[-h --help]).any? || active_command.name == 'help'
176
+ if do_help
177
+ # If there are options in addition to --help, then Commander
178
+ # runs the command! So remove all options.
179
+ #
180
+ # E.g., this shows the help for usage:
181
+ # $ murano --help usage
182
+ # but if a flag is added, the command gets run, e.g.,
183
+ # $ murano usage --id 1234 --help
184
+ # runs the usage command.
185
+ #
186
+ # I [lb] walked the code and it looks like when Commander tries to
187
+ # validate the args, it doesn't like the --id flag (maybe because
188
+ # the "help" command is being used to validate, and it does not
189
+ # define any options?). Then it removes the --id switch (after the
190
+ # --help switch was previously removed) and tries the command *again*
191
+ # (in gems/commander-4.4.3/lib/commander/runner.rb, look for the
192
+ # comment, "Remove the offending args and retry"). So in the example
193
+ # given above, `murano usage --id 1234 --help`, both the `--id` flag
194
+ # and `--help` flag are moved from @args, and then `murano usage 1234`
195
+ # is executed (and args are not validated by Commander but merely passed
196
+ # to the command action, so `usage` gets args=['1234']). Whadda wreck.
197
+ reject_next = false
198
+ @args.reject! do |arg|
199
+ reject = false
200
+ if reject_next
201
+ reject = true
202
+ reject_next = false
203
+ elsif arg.start_with?('-') && !help_opts.include?(arg) && !vers_opts.include?(arg)
204
+ reject = true
205
+ # See if the next argument should also be consumed.
206
+ arg_opts = @options.select do |opt|
207
+ if arg =~ /^-[^-]/
208
+ # Single char abbrev: look for exact match.
209
+ opt[:switches].include?(arg)
210
+ else
211
+ # Long switch: Commander matches abbrevs of longs...
212
+ opt[:switches].any? { |oarg| oarg =~ /^#{arg}/ }
213
+ end
214
+ end
215
+ if arg =~ /^--/
216
+ exact = arg_opts.select do |opt|
217
+ opt[:switches].include?(arg) || opt[:switches].any? do |sw|
218
+ sw.start_with?(arg + ' ')
219
+ end
220
+ end
221
+ arg_opts = exact unless exact.empty?
222
+ end
223
+ if arg_opts.length > 1
224
+ # MAYBE/2017-08-23: Always do this check, not just for help.
225
+ ambig = MrMurano::Verbose.fancy_ticks(arg)
226
+ match = arg_opts.map do |opt|
227
+ opt[:switches].map { |sw| MrMurano::Verbose.fancy_ticks(sw) }.join('|')
228
+ end
229
+ match = match.flatten
230
+ if match.length > 1
231
+ match[-1] = "and #{match[-1]}"
232
+ end
233
+ match = match.join(', ')
234
+ MrMurano::Verbose.error("Ambiguous option: #{ambig} matches: #{match}")
235
+ exit 2
236
+ elsif arg_opts.length == 1
237
+ if arg_opts.first[:switches].any? { |opt| opt.start_with?('--') && opt.include?(' ') }
238
+ # There's a space in the --long setting, e.g., '--config KEY=VAL',
239
+ # so we know there's an argument following.
240
+ reject_next = true
241
+ end
242
+ end
243
+ end
244
+ reject
140
245
  end
141
- exit 2
142
246
  end
247
+ @purargs = @args - help_opts
248
+ return do_help if active_command.name == 'help'
249
+ # Any command other than `murano help` or `murano --help`.
250
+ return do_help if !do_help || active_command.name.include?(' ')
251
+ # This is a single-word command, e.g., 'link', not 'link list',
252
+ # as in `murano link --help`, not `murano link list --help`.
253
+ # Positional parameters break Commander. E.g.,
254
+ # $ murano --help config application.id
255
+ # invalid command. Use --help for more information
256
+ # so remove any remaining --options and use the first term.
257
+ @args -= help_opts
258
+ @args = @args[0..0]
259
+ # Add back in the --help if the command is not a subcommand help.
260
+ @args.push('--help') unless active_command.subcmdgrouphelp
261
+ do_help
143
262
  end
144
263
 
145
- # Weird. --help doesn't work if other flags also specified.
146
- # E.g., this shows the help for usage:
147
- # $ murano --help usage
148
- # but if a flag is added, the command gets run, e.g.,
149
- # $ murano usage --id 1234 --help
150
- # ignores the --help and runs the usage command.
151
- # ([lb] walked the code and it looks like when Commander tries to
152
- # validate the args, it doesn't like the --id flag (maybe because
153
- # the "help" command is being used to validate?). Then it removes
154
- # the --id flags (after --help was previously removed) and tries
155
- # the command *again* (in gems/commander-4.4.3/lib/commander/runner.rb,
156
- # look for the comment, "Remove the offending args and retry").
157
- #
158
- # 2017-06-14: [lb] tried to override run! here to show help correctly
159
- # in this use case, but I could not get it to work. Oh, well... a
160
- # minor annoyance; just live with it, I guess.
264
+ def show_alias_help_maybe!
265
+ return unless alias?(command_name_from_args) || (active_command.name == 'help' && @args.length > 1)
266
+ # Why, oh why, Commander, do you flake out on aliases?
267
+ # E.g.,
268
+ # $ murano product --help
269
+ # invalid command. Use --help for more information
270
+ # Though it sometimes work, like with:
271
+ # $ murano --help product device enable
272
+ # but only because Commander shows help for the 'device' command.
273
+ # I.e., this doesn't work: `murano product push --help`
274
+ # So we'll just roll our own help for aliases!
275
+ @args -= help_opts
276
+ cli_cmd = MrMurano::Verbose.fancy_ticks(@purargs.join(' '))
277
+ if active_command.name == 'help'
278
+ arg_cmd = @args.join(' ')
279
+ else
280
+ arg_cmd = command_name_from_args
281
+ end
282
+ mur_msg = ''
283
+ if @aliases[arg_cmd].nil?
284
+ matches = @aliases.keys.find_all { |key| key.start_with?('arg_cmd') }
285
+ matches = @aliases.keys.find_all { |key| key.start_with?(@args[0]) } if matches.empty?
286
+ unless matches.empty?
287
+ matches = matches.map { |match| MrMurano::Verbose.fancy_ticks(match) }
288
+ matches = matches.sort.join(', ')
289
+ mur_msg = %(The #{cli_cmd} command includes: #{matches})
290
+ end
291
+ else
292
+ mur_cmd = []
293
+ mur_cmd += [active_command.name] if active_command.name != 'help'
294
+ mur_cmd += @aliases[arg_cmd] unless @aliases[arg_cmd].empty?
295
+ mur_cmd = mur_cmd.join(' ')
296
+ #mur_cmd = active_command.name if mur_cmd.empty?
297
+ mur_cmd = MrMurano::Verbose.fancy_ticks(mur_cmd)
298
+ mur_msg = %(The #{cli_cmd} command is really: #{mur_cmd})
299
+ end
300
+ return if mur_msg.empty?
301
+ puts mur_msg
302
+ exit 0
303
+ end
161
304
  end
162
305
  end
163
306
 
@@ -1,3 +1,10 @@
1
+ # Last Modified: 2017.08.20 /coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2016-2017 Exosite LLC.
5
+ # License: MIT. See LICENSE.txt.
6
+ # vim:tw=0:ts=2:sw=2:et:ai
7
+
1
8
  require 'MrMurano/verbosing'
2
9
 
3
10
  module MrMurano
@@ -18,50 +25,41 @@ module MrMurano
18
25
  def mapservice(service)
19
26
  service = service.to_s.downcase
20
27
  SERVICE_MAP.each_pair do |k, v|
21
- if service == k.downcase || service == v.downcase
22
- return v
23
- end
28
+ return v if [k.downcase, v.downcase].include? service
24
29
  end
25
- return service.sub(/(.)(.*)/){"#{$1.upcase}#{$2.downcase}"}
30
+ # rubocop:disable Style/PerlBackrefs: "Avoid the use of Perl-style backrefs."
31
+ # Because of the upcase, we cannot just call, e.g.,
32
+ # service.sub(/(.)(.*)/, "\\1\\2")
33
+ service.sub(/(.)(.*)/) { "#{$1.upcase}#{$2.downcase}" }
26
34
  end
27
35
 
28
36
  def read(service, setting)
29
- begin
30
- debug %{Looking up class "MrMurano::#{mapservice(service)}::Settings"}
31
- gb = Object::const_get("MrMurano::#{mapservice(service)}::Settings").new
32
- meth = setting.to_sym
33
- debug %{Looking up method "#{meth}"}
34
- if gb.respond_to?(meth)
35
- return gb.__send__(meth)
36
- else
37
- error "Unknown setting '#{setting}' on '#{service}'"
38
- end
39
- rescue NameError => e
40
- error "No Settings on \"#{service}\""
41
- if $cfg['tool.debug'] then
42
- error e.message
43
- error e.to_s
44
- end
37
+ debug %(Looking up class "MrMurano::#{mapservice(service)}::Settings")
38
+ gb = Object.const_get("MrMurano::#{mapservice(service)}::Settings").new
39
+ meth = setting.to_sym
40
+ debug %(Looking up method "#{meth}")
41
+ return gb.__send__(meth) if gb.respond_to?(meth)
42
+ error "Unknown setting '#{setting}' on '#{service}'"
43
+ rescue NameError => e
44
+ error %(No Settings on "#{service}")
45
+ if $cfg['tool.debug']
46
+ error e.message
47
+ error e.to_s
45
48
  end
46
49
  end
47
50
 
48
51
  def write(service, setting, value)
49
- begin
50
- debug %(Looking up class "MrMurano::#{mapservice(service)}::Settings")
51
- gb = Object::const_get("MrMurano::#{mapservice(service)}::Settings").new
52
- meth = "#{setting}=".to_sym
53
- debug %(Looking up method "#{meth}")
54
- if gb.respond_to? meth then
55
- return gb.__send__(meth, value)
56
- else
57
- error "Unknown setting '#{setting}' on '#{service}'"
58
- end
59
- rescue NameError => e
60
- error %(No Settings on "#{service}")
61
- if $cfg['tool.debug'] then
62
- error e.message
63
- error e.to_s
64
- end
52
+ debug %(Looking up class "MrMurano::#{mapservice(service)}::Settings")
53
+ gb = Object.const_get("MrMurano::#{mapservice(service)}::Settings").new
54
+ meth = "#{setting}=".to_sym
55
+ debug %(Looking up method "#{meth}")
56
+ return gb.__send__(meth, value) if gb.respond_to? meth
57
+ error "Unknown setting '#{setting}' on '#{service}'"
58
+ rescue NameError => e
59
+ error %(No Settings on "#{service}")
60
+ if $cfg['tool.debug']
61
+ error e.message
62
+ error e.to_s
65
63
  end
66
64
  end
67
65
 
@@ -74,9 +72,11 @@ module MrMurano
74
72
  result = {}
75
73
  ::MrMurano.constants.each do |maybe|
76
74
  begin
77
- gb = Object::const_get("MrMurano::#{maybe}::Settings")
78
- result[maybe] = gb.instance_methods(false).select{|i| i.to_s[-1] != '='}
75
+ gb = Object.const_get("MrMurano::#{maybe}::Settings")
76
+ result[maybe] = gb.instance_methods(false).reject { |i| i.to_s[-1] == '=' }
77
+ # rubocop:disable Lint/HandleExceptions: Do not suppress exceptions."
79
78
  rescue
79
+ # EXPLAIN/2017-08-20: When/Why does this happen?
80
80
  end
81
81
  end
82
82
  result
@@ -86,5 +86,3 @@ module MrMurano
86
86
  end
87
87
  end
88
88
 
89
- # vim: set ai et sw=2 ts=2 :
90
-
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.17 /coding: utf-8
1
+ # Last Modified: 2017.08.22 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -70,6 +70,7 @@ module MrMurano
70
70
  end
71
71
 
72
72
  def remove(id)
73
+ return unless remove_item_allowed(id)
73
74
  delete("/#{id}")
74
75
  end
75
76
 
@@ -1,4 +1,4 @@
1
- # Last Modified: 2017.08.18 /coding: utf-8
1
+ # Last Modified: 2017.08.23 /coding: utf-8
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Copyright © 2016-2017 Exosite LLC.
@@ -54,6 +54,7 @@ module MrMurano
54
54
 
55
55
  # ??? remove
56
56
  def remove(name)
57
+ return unless remove_item_allowed(name)
57
58
  delete('/' + name)
58
59
  end
59
60
 
@@ -69,6 +70,9 @@ module MrMurano
69
70
  raise 'no file' unless thereitem.script
70
71
  script = thereitem.script
71
72
  end
73
+
74
+ script = config_vars_decode(script)
75
+
72
76
  localpath = Pathname.new(localpath) unless localpath.is_a?(Pathname)
73
77
  name = mkname(thereitem)
74
78
  pst = thereitem.to_h.merge(
@@ -79,10 +83,18 @@ module MrMurano
79
83
  name: name,
80
84
  )
81
85
  debug "f: #{localpath} >> #{pst.reject { |k, _| k == :script }.to_json}"
86
+ therealias = mkalias(thereitem)
87
+ upload_script(therealias, name, localpath, pst)
88
+ end
89
+
90
+ def upload_script(therealias, name, localpath, pst)
82
91
  # Try PUT. If 404, then POST.
83
92
  # I.e., PUT if not exists, else POST to create.
84
93
  updated_at = nil
85
- put('/' + mkalias(thereitem), pst) do |request, http|
94
+
95
+ return unless upload_item_allowed(therealias)
96
+
97
+ put('/' + therealias, pst) do |request, http|
86
98
  response = http.request(request)
87
99
  isj, jsn = isJSON(response.body)
88
100
  # ORDER: An HTTPNoContent is also a HTTPSuccess, so the latter comes first.
@@ -181,8 +193,6 @@ module MrMurano
181
193
  cache_file = $cfg.file_at(cache_file_name)
182
194
  if cache_file.file?
183
195
  cache_file.open('r+') do |io|
184
- # FIXME/2017-07-02: "Security/YAMLLoad: Prefer using YAML.safe_load over YAML.load."
185
- # rubocop:disable Security/YAMLLoad
186
196
  cache = YAML.load(io)
187
197
  cache = {} unless cache
188
198
  io.rewind
@@ -205,7 +215,6 @@ module MrMurano
205
215
  return nil unless cache_file.file?
206
216
  ret = nil
207
217
  cache_file.open('r') do |io|
208
- # FIXME/2017-07-02: "Security/YAMLLoad: Prefer using YAML.safe_load over YAML.load."
209
218
  cache = YAML.load(io)
210
219
  return nil unless cache
211
220
  if cache.key?(local_path.to_s)
@@ -250,7 +259,6 @@ module MrMurano
250
259
  end
251
260
 
252
261
  def initialize(sid=nil)
253
- # FIXME/VERIFY/2017-07-02: Check that products do not have Modules.
254
262
  @solntype = 'application.id'
255
263
  super
256
264
  @uriparts << :module
@@ -307,14 +315,14 @@ module MrMurano
307
315
  root = root.expand_path
308
316
  if path.basename.sub(/\.lua$/i, '').to_s.include?('.')
309
317
  warning(
310
- "WARNING: Do not use periods in filenames. Rename: ‘#{path.basename}"
318
+ "WARNING: Do not use periods in filenames. Rename: #{fancy_ticks(path.basename)}"
311
319
  )
312
320
  end
313
321
  path.dirname.ascend do |ancestor|
314
322
  break if ancestor == root
315
323
  if ancestor.basename.to_s.include?('.')
316
324
  warning(
317
- "WARNING: Do not use periods in directory names. Rename: ‘#{ancestor.basename}"
325
+ "WARNING: Do not use periods in directory names. Rename: #{fancy_ticks(ancestor.basename)}"
318
326
  )
319
327
  end
320
328
  end
@@ -349,7 +357,12 @@ module MrMurano
349
357
  attr_accessor :event
350
358
  # @return [String] For device2 events, the type of event.
351
359
  attr_accessor :type
360
+ # @return [String] Service alias, e.g., "{product.id}"
361
+ attr_accessor :svc_alias
352
362
  # @return [Boolean] True if local phantom item via eventhandler.undeletable.
363
+ # I.e., the file does not exist locally and is the
364
+ # empty string on the server, so locally considered
365
+ # empty string, too.
353
366
  attr_accessor :phantom
354
367
  end
355
368
 
@@ -373,6 +386,10 @@ module MrMurano
373
386
  [remote[:service], remote[:event]].join('_')
374
387
  end
375
388
 
389
+ def synckey(item)
390
+ "#{item[:service]}_#{item[:event]}"
391
+ end
392
+
376
393
  def list(call=nil, data=nil, &block)
377
394
  ret = get(call, data, &block)
378
395
  return [] unless ret.is_a?(Hash) && !ret.key?(:error)
@@ -386,9 +403,36 @@ module MrMurano
386
403
  toss
387
404
  end
388
405
  items.map { |item| EventHandlerItem.new(item) }
389
- # MAYBE/2017-08-17:
390
- # items.map! ...
391
- # sort_by_name(items)
406
+ # MAYBE/2017-08-17:
407
+ # items.map! ...
408
+ # sort_by_name(items)
409
+ end
410
+
411
+ def resolve_config_var_usage!(there, local)
412
+ # Substitute '{product.id}' for the actual product.id if a corresponding
413
+ # local file does not exist, or if the local file already uses the alias.
414
+ encode_items = {}
415
+ local.each do |item|
416
+ encode_items[item.service] = {} if encode_items[item.service].nil?
417
+ encode_items[item.service][item.event] = item.svc_alias
418
+ end
419
+ there.map! do |item|
420
+ if (
421
+ !encode_items[item.service].nil? &&
422
+ !encode_items[item.service][item.event].nil?
423
+ )
424
+ svc_alias = encode_items[item.service][item.event]
425
+ else
426
+ # There's no corresponding local item.
427
+ _encoded, svc_alias = encode_config_var(item.service)
428
+ end
429
+ svc_alias = nil if svc_alias == item.service
430
+ unless svc_alias.nil?
431
+ item.svc_alias = svc_alias
432
+ item.name.sub!($cfg[svc_alias], "{#{svc_alias}}")
433
+ end
434
+ item
435
+ end
392
436
  end
393
437
 
394
438
  def skip?(item, skiplist)
@@ -400,15 +444,19 @@ module MrMurano
400
444
 
401
445
  def cmp_svc_evt(item, svc_evt)
402
446
  service, event = svc_evt.split('.', 2)
447
+ svc_match = (
448
+ service == item[:service] ||
449
+ (!item[:svc_alias].to_s.empty? && service == item[:svc_alias])
450
+ )
403
451
  if event.nil? || item[:event] == '*'
404
- service == item[:service]
452
+ svc_match
405
453
  else
406
454
  # rubocop:disable Style/IfInsideElse
407
455
  #svc_evt == "#{item[:service]}.#{item[:event]}"
408
456
  if service == '*'
409
457
  event == item[:event]
410
458
  else
411
- service == item[:service] && event == item[:event]
459
+ svc_match && event == item[:event]
412
460
  end
413
461
  end
414
462
  end
@@ -462,35 +510,33 @@ module MrMurano
462
510
  # @match_header finds a service and an event string, e.g., "--EVENT svc evt\n"
463
511
  md = @match_header.match(line)
464
512
  if !md.nil?
465
- # FIXME/2017-08-09: device2.event is now in the skiplist,
466
- # but some tests have a "device2 data_in" script, which
467
- # gets changed to "device2.event" here and then uploaded
468
- # (note that skiplist does not apply to local items).
469
- # You can test this code via:
470
- # rspec ./spec/cmd_syncdown_spec.rb
471
- # which has a fixture with device2.data_in specified.
472
- # QUESTION: Does writing device2.event do anything?
473
- # You cannot edit that handler from the web UI...
474
- # - Should we change the test?
475
- # - Should we get rid of this device2 hack?
476
- if md[:service] == 'device2'
477
- event_event = 'event'
478
- event_type = md[:event]
479
- # FIXME/CONFIRM/2017-07-02: 'data_in' was the old event name? It's now 'event'?
480
- # Want this?:
481
- # event_type = 'event' if event_type == 'data_in'
482
- else
483
- event_event = md[:event]
484
- event_type = nil
485
- end
486
- # header line.
513
+ service, config_var = decode_config_var(md[:service])
514
+ event_event, event_type = resolve_event_type(service, md[:event])
515
+ # Header line.
516
+ svc_alias = config_var if service != md[:service]
487
517
  cur = EventHandlerItem.new(
488
- service: md[:service],
489
- event: event_event,
490
- type: event_type,
518
+ #name
491
519
  local_path: path,
492
- line: lineno,
520
+ #id
493
521
  script: line,
522
+ line: lineno,
523
+ #line_end
524
+ #diff
525
+ #selected
526
+ #synckey
527
+ #synctype
528
+ #type
529
+ #updated_at
530
+ #dup_count
531
+ #alias
532
+ #updated_at
533
+ #created_at
534
+ #solution_id
535
+ service: service,
536
+ event: event_event,
537
+ type: event_type,
538
+ #phantom
539
+ svc_alias: svc_alias,
494
540
  )
495
541
  elsif !cur.nil? && !cur[:script].nil?
496
542
  # 2017-07-02: Frozen string literal: change << to +=
@@ -501,24 +547,27 @@ module MrMurano
501
547
  cur[:line_end] = lineno unless cur.nil?
502
548
 
503
549
  # If cur is nil here, then we need to do a :legacy check.
504
- if cur.nil? && $project['services.legacy'].is_a?(Hash)
505
- spath = path.relative_path_from(from)
506
- debug "No headers: #{spath}"
507
- service, event = $project['services.legacy'][spath.to_s]
508
- debug "Legacy lookup #{spath} => [#{service}, #{event}]"
509
- unless service.nil? || event.nil?
510
- warning %(Event in #{spath} missing header, but has legacy support.)
511
- warning %(Please add the header "--#EVENT #{service} #{event}")
512
- cur = EventHandlerItem.new(
513
- service: service,
514
- event: event,
515
- type: nil,
516
- local_path: path,
517
- line: 0,
518
- line_end: lineno,
519
- script: path.read, # FIXME: ick, fix this.
520
- )
521
- end
550
+ to_remote_item_legacy_check(cur, path, from, lineno)
551
+ end
552
+
553
+ def to_remote_item_legacy_check(cur, path, from, lineno)
554
+ return cur unless cur.nil? && $project['services.legacy'].is_a?(Hash)
555
+ spath = path.relative_path_from(from)
556
+ debug "No headers: #{spath}"
557
+ service, event = $project['services.legacy'][spath.to_s]
558
+ debug "Legacy lookup #{spath} => [#{service}, #{event}]"
559
+ unless service.nil? || event.nil?
560
+ warning %(Event in #{spath} missing header, but has legacy support.)
561
+ warning %(Please add the header "--#EVENT #{service} #{event}")
562
+ cur = EventHandlerItem.new(
563
+ service: service,
564
+ event: event,
565
+ type: nil,
566
+ local_path: path,
567
+ line: 0,
568
+ line_end: lineno,
569
+ script: path.read, # FIXME: ick, fix this.
570
+ )
522
571
  end
523
572
  cur
524
573
  end
@@ -529,13 +578,99 @@ module MrMurano
529
578
  md = pattern_pattern.match(pattern)
530
579
  return false if md.nil?
531
580
  debug "match pattern: '#{md[:service]}' '#{md[:event]}'"
532
- return false unless md[:service].empty? || item[:service].casecmp(md[:service]).zero?
581
+ if (
582
+ !md[:service].empty? &&
583
+ !item[:service].casecmp(md[:service]).zero? &&
584
+ (item[:svc_alias].to_s.empty? || !item[:svc_alias].casecmp(md[:service]).zero?)
585
+ )
586
+ return false
587
+ end
533
588
  return false unless md[:event].empty? || item[:event].casecmp(md[:event]).zero?
534
- true # Both match (or are empty.)
589
+ true # Both match (or both empty).
535
590
  end
536
591
 
537
- def synckey(item)
538
- "#{item[:service]}_#{item[:event]}"
592
+ def config_vars_decode(script)
593
+ config_vars_translate(script, method(:decode_config_var))
594
+ end
595
+
596
+ def config_vars_encode(script)
597
+ config_vars_translate(script, method(:encode_config_var))
598
+ end
599
+
600
+ def config_vars_translate(script, codec)
601
+ new_script = ''
602
+ # Replace the service with a {config.variable} if appropriate.
603
+ script.lines.each do |line|
604
+ # @match_header finds a service and an event string, e.g., "--EVENT svc evt\n"
605
+ md = @match_header.match(line)
606
+ unless md.nil?
607
+ service, _config_var = codec.call(md[:service])
608
+ line = line.gsub(
609
+ /--#EVENT (?<service>\S+) (?<event>\S+)/,
610
+ "--#EVENT #{service} \\k<event>"
611
+ )
612
+ end
613
+ new_script += line
614
+ end
615
+ new_script
616
+ end
617
+
618
+ def decode_config_var(term)
619
+ decoded = term
620
+ config_var = nil
621
+ # Try to match svc against a {config.variable}.
622
+ mat = /^\{([^\.}]+)\.([^\.}]+)\}$/.match(term)
623
+ unless mat.nil?
624
+ # User specified a config value, e.g., "--#EVENT {product.id} event".
625
+ config_var = "#{mat[1]}.#{mat[2]}"
626
+ decoded = $cfg[config_var]
627
+ if decoded.nil?
628
+ warning "Config value not found for variable: #{fancy_ticks(term)}"
629
+ # The platform should complain about file not found,
630
+ # and we'll process everything else.
631
+ decoded = term
632
+ config_var = nil
633
+ end
634
+ end
635
+ [decoded, config_var]
636
+ end
637
+
638
+ def encode_config_var(term)
639
+ encoded = term
640
+ config_var = nil
641
+ # MEH/2017-08-22: For now, only matching against one config var.
642
+ # Maybe in the future we'd want to examine all the config vars?
643
+ # MAGIC_STRING: {config.variable} substitution.
644
+ if term == $cfg['product.id']
645
+ encoded = '{product.id}'
646
+ config_var = 'product.id'
647
+ end
648
+ [encoded, config_var]
649
+ end
650
+
651
+ def resolve_event_type(service, event)
652
+ # FIXME/2017-08-09: device2.event is now in the skiplist,
653
+ # but some tests have a "device2 data_in" script, which
654
+ # gets changed to "device2.event" here and then uploaded
655
+ # (note that skiplist does not apply to local items).
656
+ # You can test this code via:
657
+ # rspec ./spec/cmd_syncdown_spec.rb
658
+ # which has a fixture with device2.data_in specified.
659
+ # QUESTION: Does writing device2.event do anything?
660
+ # You cannot edit that handler from the web UI...
661
+ # - Should we change the test?
662
+ # - Should we get rid of this device2 hack?
663
+ if service == 'device2'
664
+ event_event = 'event'
665
+ event_type = event
666
+ # FIXME/CONFIRM/2017-07-02: 'data_in' was the old event name? It's now 'event'?
667
+ # Want this?:
668
+ # event_type = 'event' if event_type == 'data_in'
669
+ else
670
+ event_event = event
671
+ event_type = nil
672
+ end
673
+ [event_event, event_type]
539
674
  end
540
675
 
541
676
  def resurrect_undeletables(localbox, therebox)