MuranoCLI 3.2.0.beta.9 → 3.2.1.pre.beta.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/Rakefile +5 -0
  4. data/dockers/README.rst +7 -0
  5. data/dockers/RELEASE.rst +6 -3
  6. data/dockers/docker-test.sh +45 -17
  7. data/docs/completions/murano_completion-bash +211 -86
  8. data/lib/MrMurano/Account.rb +72 -4
  9. data/lib/MrMurano/Business.rb +163 -2
  10. data/lib/MrMurano/Commander-Entry.rb +1 -2
  11. data/lib/MrMurano/Config.rb +19 -18
  12. data/lib/MrMurano/Content.rb +26 -19
  13. data/lib/MrMurano/Gateway.rb +51 -10
  14. data/lib/MrMurano/ReCommander.rb +1 -1
  15. data/lib/MrMurano/Solution-Services.rb +80 -35
  16. data/lib/MrMurano/Solution-Users.rb +1 -0
  17. data/lib/MrMurano/SyncRoot.rb +10 -3
  18. data/lib/MrMurano/SyncUpDown-Core.rb +47 -36
  19. data/lib/MrMurano/SyncUpDown-Item.rb +46 -14
  20. data/lib/MrMurano/SyncUpDown.rb +22 -20
  21. data/lib/MrMurano/Webservice-Endpoint.rb +20 -18
  22. data/lib/MrMurano/Webservice-File.rb +63 -20
  23. data/lib/MrMurano/commands/business.rb +14 -1
  24. data/lib/MrMurano/commands/child.rb +148 -0
  25. data/lib/MrMurano/commands/devices.rb +298 -149
  26. data/lib/MrMurano/commands/element.rb +2 -1
  27. data/lib/MrMurano/commands/globals.rb +3 -0
  28. data/lib/MrMurano/commands/network.rb +152 -33
  29. data/lib/MrMurano/commands/sync.rb +2 -2
  30. data/lib/MrMurano/commands.rb +1 -0
  31. data/lib/MrMurano/verbosing.rb +13 -2
  32. data/lib/MrMurano/version.rb +1 -1
  33. data/spec/Account_spec.rb +43 -11
  34. data/spec/Content_spec.rb +5 -3
  35. data/spec/GatewayBase_spec.rb +1 -1
  36. data/spec/GatewayDevice_spec.rb +47 -8
  37. data/spec/GatewayResource_spec.rb +1 -1
  38. data/spec/GatewaySettings_spec.rb +1 -1
  39. data/spec/HttpAuthed_spec.rb +17 -3
  40. data/spec/ProjectFile_spec.rb +59 -23
  41. data/spec/Setting_spec.rb +2 -1
  42. data/spec/Solution-ServiceConfig_spec.rb +1 -1
  43. data/spec/Solution-ServiceEventHandler_spec.rb +27 -20
  44. data/spec/Solution-ServiceModules_spec.rb +7 -5
  45. data/spec/Solution-UsersRoles_spec.rb +7 -1
  46. data/spec/Solution_spec.rb +9 -1
  47. data/spec/SyncRoot_spec.rb +5 -5
  48. data/spec/SyncUpDown_spec.rb +262 -211
  49. data/spec/Verbosing_spec.rb +49 -8
  50. data/spec/Webservice-Cors_spec.rb +10 -1
  51. data/spec/Webservice-Endpoint_spec.rb +84 -65
  52. data/spec/Webservice-File_spec.rb +16 -11
  53. data/spec/Webservice-Setting_spec.rb +7 -1
  54. data/spec/_workspace.rb +9 -0
  55. data/spec/cmd_business_spec.rb +5 -10
  56. data/spec/cmd_common.rb +67 -32
  57. data/spec/cmd_config_spec.rb +9 -14
  58. data/spec/cmd_content_spec.rb +15 -26
  59. data/spec/cmd_cors_spec.rb +9 -12
  60. data/spec/cmd_device_spec.rb +31 -45
  61. data/spec/cmd_domain_spec.rb +12 -10
  62. data/spec/cmd_element_spec.rb +18 -17
  63. data/spec/cmd_exchange_spec.rb +1 -4
  64. data/spec/cmd_init_spec.rb +56 -72
  65. data/spec/cmd_keystore_spec.rb +17 -26
  66. data/spec/cmd_link_spec.rb +13 -17
  67. data/spec/cmd_password_spec.rb +9 -10
  68. data/spec/cmd_setting_application_spec.rb +95 -68
  69. data/spec/cmd_setting_product_spec.rb +59 -37
  70. data/spec/cmd_status_spec.rb +46 -84
  71. data/spec/cmd_syncdown_application_spec.rb +28 -50
  72. data/spec/cmd_syncdown_both_spec.rb +44 -93
  73. data/spec/cmd_syncdown_unit_spec.rb +858 -0
  74. data/spec/cmd_syncup_spec.rb +21 -56
  75. data/spec/cmd_token_spec.rb +0 -3
  76. data/spec/cmd_usage_spec.rb +15 -10
  77. data/spec/dry_run_formatter.rb +1 -0
  78. data/spec/fixtures/dumped_config +4 -4
  79. data/spec/spec_helper.rb +3 -0
  80. metadata +4 -2
@@ -17,6 +17,7 @@ require 'MrMurano/Config'
17
17
  require 'MrMurano/SolutionId'
18
18
  require 'MrMurano/SyncRoot'
19
19
  require 'MrMurano/SyncUpDown'
20
+ require 'MrMurano/SyncUpDown-Item'
20
21
 
21
22
  module MrMurano
22
23
  ## The details of talking to the Gateway [Device2] service.
@@ -114,6 +115,33 @@ module MrMurano
114
115
  class Resources < GweBase
115
116
  include SyncUpDown
116
117
 
118
+ class GatewayItem < Item
119
+ # @return [String] The type of data stored in aliases for this resource.
120
+ # (string|boolean|number)
121
+ attr_accessor :format
122
+ # @return [String] Helpful unit description for the alias.
123
+ attr_accessor :unit
124
+ # @return [Boolean] List of data format validations.
125
+ attr_accessor :settable
126
+ # @return [Array] True if the cloud can write to this.
127
+ attr_accessor :allowed
128
+ # @return [String] The resource alias.
129
+ attr_accessor :alias
130
+
131
+ def reject_ephemeral!
132
+ super.reject do |attr_key, _|
133
+ [
134
+ # Server-siders:
135
+ # :format,
136
+ # :unit,
137
+ # :settable,
138
+ # :allowed,
139
+ # :alias,
140
+ ].include? attr_key
141
+ end
142
+ end
143
+ end
144
+
117
145
  def initialize
118
146
  super
119
147
  @itemkey = :alias
@@ -132,7 +160,7 @@ module MrMurano
132
160
  # convert hash to array.
133
161
  res = []
134
162
  ret[:resources].each_pair do |key, value|
135
- res << value.merge(alias: key.to_s)
163
+ res << GatewayItem.new(value.merge(alias: key.to_s))
136
164
  end
137
165
  res
138
166
  # MAYBE/2017-08-17:
@@ -144,7 +172,8 @@ module MrMurano
144
172
  res = {}
145
173
  data.each do |value|
146
174
  key = value[:alias]
147
- res[key] = value.reject { |k, _v| k == :alias }
175
+ # (lb): Item has reject method, but convert to Hash, for patch.
176
+ res[key] = value.to_h.reject { |k, _v| k == :alias }
148
177
  end
149
178
 
150
179
  patch('', resources: res)
@@ -194,8 +223,6 @@ module MrMurano
194
223
 
195
224
  def syncdown_before
196
225
  super
197
- # TEST/2017-07-02: Could there be duplicate gateway items?
198
- # [lb] just added code to SyncUpDown.locallist and is curious.
199
226
  @here = locallist
200
227
  end
201
228
 
@@ -247,10 +274,11 @@ module MrMurano
247
274
  def resources_write(file_path)
248
275
  num_synced = 0
249
276
  # User can blow away specs/ directory if they want; we'll just make
250
- # a new one. [This code somewhat copy-paste from make_directory.]
277
+ # a new one. [This code is somewhat copy-paste from make_directory.]
251
278
  basedir = file_path
252
279
  basedir = basedir.dirname unless basedir.extname.empty?
253
- raise 'Unexpected: bad basedir' if basedir.to_s.empty? || basedir == File::SEPARATOR
280
+ bad_basedir = basedir.to_s.empty? || basedir == File::SEPARATOR
281
+ raise 'Unexpected: bad basedir' if bad_basedir
254
282
 
255
283
  unless basedir.exist?
256
284
  if $cfg['tool.dry']
@@ -277,7 +305,7 @@ module MrMurano
277
305
  @here.each do |value|
278
306
  key = value[:alias]
279
307
  res[key] = Hash.transform_keys_to_strings(
280
- value.reject { |k, _v| k == :alias }
308
+ value.to_h.reject { |k, _v| k == :alias }
281
309
  )
282
310
  end
283
311
  ohash = ordered_hash(res)
@@ -334,7 +362,9 @@ module MrMurano
334
362
 
335
363
  res = []
336
364
  here.each_pair do |key, value|
337
- res << Hash.transform_keys_to_symbols(value).merge(alias: key.to_s)
365
+ hash = Hash.transform_keys_to_symbols(value).merge(alias: key.to_s)
366
+ item = GatewayItem.new(hash)
367
+ res << item
338
368
  end
339
369
 
340
370
  sort_by_name(res)
@@ -398,8 +428,17 @@ module MrMurano
398
428
  # @option opts [String,Pathname,IO] :key Shared secret for hash, password, token types;
399
429
  # or public key for certificate auth type.
400
430
  # May be string or IO/Pathname to file.
401
- # @option opts [String] :type One of: certificate, hash, password, signature, token
402
- DEVICE_AUTH_TYPES = %i[certificate hash password signature token].freeze
431
+ # @option opts [String] :type One of: DEVICE_AUTH_TYPES.
432
+
433
+ DEVICE_AUTH_TYPES = %i[
434
+ certificate
435
+ hash
436
+ password
437
+ signature
438
+ token
439
+ csr
440
+ ].freeze
441
+
403
442
  def enable(id, opts=nil)
404
443
  opts = {} if opts.nil?
405
444
  props = { auth: {}, locked: false }
@@ -421,6 +460,8 @@ module MrMurano
421
460
  props[:auth][:type] = opts[:type]
422
461
  end
423
462
  unless opts[:key].nil?
463
+ # 2018-07-10: (lb): I think the `read` feature is no longer used
464
+ # (no callers pass in a File object any more).
424
465
  props[:auth][:key] = opts[:key].is_a?(String) && opts[:key] || opts[:key].read
425
466
  props[:auth][:type] = :certificate if props[:auth][:type].nil?
426
467
  end
@@ -370,7 +370,7 @@ module Commander
370
370
  def verify_solutions_unmanaged!
371
371
  return if $cfg['tool.skip-managed']
372
372
  # (lb): All @exosite.com employees are welcome behind the curtain.
373
- if $cfg['user.name'] && $cfg['user.name'].end_with?("@exosite.com")
373
+ if $cfg['user.name'] && $cfg['user.name'].end_with?('@exosite.com')
374
374
  MrMurano::Verbose.verbose(
375
375
  "Welcome behind the curtain, #{$cfg['user.name']}!"
376
376
  )
@@ -53,13 +53,14 @@ module MrMurano
53
53
  end
54
54
  end
55
55
 
56
- # ??? remove
57
- def remove(name)
58
- return unless remove_item_allowed(name)
59
- delete('/' + name)
56
+ def remove(itemkey)
57
+ return unless remove_item_allowed(itemkey)
58
+ delete('/' + itemkey)
60
59
  end
61
60
 
62
- def remove_lite(_name, item, modify=false)
61
+ def remove_or_clear(itemkey, item, modify=false)
62
+ # Note that item.phantom, well, it did, until we called reject_ephemeral!.
63
+ return unless remove_item_allowed(itemkey)
63
64
  # This is a :phantom item, which has a default script, e.g.,
64
65
  # item[:script] => "--#EVENT timer timer\n"
65
66
  localpath = tolocalpath(location, item)
@@ -71,7 +72,6 @@ module MrMurano
71
72
  def upload(localpath, thereitem, _modify=false)
72
73
  localpath = Pathname.new(localpath) unless localpath.is_a?(Pathname)
73
74
  if localpath.exist?
74
- # we assume these are small enough to slurp.
75
75
  script = localpath.read
76
76
  else
77
77
  # I.e., thereitem.phantom, an "undeletable" file that does not
@@ -84,18 +84,19 @@ module MrMurano
84
84
  script = config_vars_decode(script)
85
85
 
86
86
  name = mkname(thereitem)
87
- pst = thereitem.to_h.merge(
87
+ req_body = thereitem.to_h.merge(
88
88
  solution_id: @api_id,
89
89
  script: script,
90
90
  alias: mkalias(thereitem),
91
91
  name: name,
92
92
  )
93
- debug "f: #{localpath} >> #{pst.reject { |k, _| k == :script }.to_json}"
93
+
94
+ debug "f: #{localpath} >> #{req_body.reject { |k, _| k == :script }.to_json}"
94
95
  therealias = mkalias(thereitem)
95
- upload_script(therealias, name, localpath, pst)
96
+ upload_script(therealias, name, localpath, req_body)
96
97
  end
97
98
 
98
- def upload_script(therealias, name, localpath, pst)
99
+ def upload_script(therealias, name, localpath, req_body)
99
100
  # Try PUT. If 404, then POST.
100
101
  # I.e., PUT if not exists, else POST to create.
101
102
  updated_at = nil
@@ -105,7 +106,7 @@ module MrMurano
105
106
  get_again = false
106
107
  create_it = false
107
108
 
108
- put('/' + therealias, pst) do |request, http|
109
+ put('/' + therealias, req_body) do |request, http|
109
110
  response = http.request(request)
110
111
  isj, jsn = isJSON(response.body)
111
112
  # ORDER: An HTTPNoContent is also a HTTPSuccess, so the latter comes first.
@@ -131,7 +132,10 @@ module MrMurano
131
132
  create_it = true
132
133
  else
133
134
  relpath = localpath.sub(File.join(Dir.pwd, ''), '')
134
- if response.is_a?(Net::HTTPBadRequest) && isj && jsn[:message] == 'Validation errors'
135
+ if (
136
+ response.is_a?(Net::HTTPBadRequest) &&
137
+ isj && jsn[:message] == 'Validation errors'
138
+ )
135
139
  warning "Validation errors detected in #{relpath}:"
136
140
  puts MrMurano::Pretties.makeJsonPretty(
137
141
  jsn[:errors], Struct.new(:pretty).new(true),
@@ -141,6 +145,7 @@ module MrMurano
141
145
  end
142
146
  warning "Failed to upload: #{relpath}"
143
147
  end
148
+ response
144
149
  end
145
150
  if get_again
146
151
  ret = get('/' + CGI.escape(name))
@@ -150,7 +155,7 @@ module MrMurano
150
155
  warning "Failed to verify updated_at: #{ret}"
151
156
  end
152
157
  end
153
- post('/', pst) if create_it
158
+ post('/', req_body) if create_it
154
159
  cache_update_time_for(localpath, updated_at)
155
160
  end
156
161
 
@@ -285,12 +290,20 @@ module MrMurano
285
290
  class ModuleItem < Item
286
291
  # @return [String] Internal Alias name
287
292
  attr_accessor :alias
288
- # @return [String] Timestamp when this was updated.
289
- attr_accessor :updated_at
290
293
  # @return [String] Timestamp when this was created.
291
294
  attr_accessor :created_at
292
295
  # @return [String] The application solution's ID.
293
296
  attr_accessor :solution_id
297
+
298
+ def reject_ephemeral!
299
+ super.reject do |attr_key, _|
300
+ [
301
+ :alias,
302
+ :created_at,
303
+ :solution_id,
304
+ ].include? attr_key
305
+ end
306
+ end
294
307
  end
295
308
 
296
309
  def initialize(api_id=nil)
@@ -335,13 +348,14 @@ module MrMurano
335
348
  # sort_by_name(ret[:items])
336
349
  end
337
350
 
338
- def to_remote_item(root, path)
351
+ def to_remote_items(root, path)
339
352
  if $cfg['modules.no-nesting']
340
353
  name = path.basename.to_s.sub(/\..*/, '')
341
354
  else
342
355
  name = remote_item_nested_name(root, path)
343
356
  end
344
- ModuleItem.new(name: name)
357
+ item = ModuleItem.new(name: name)
358
+ [item]
345
359
  end
346
360
 
347
361
  def remote_item_nested_name(root, path)
@@ -349,14 +363,16 @@ module MrMurano
349
363
  root = root.expand_path
350
364
  if path.basename.sub(/\.lua$/i, '').to_s.include?('.')
351
365
  warning(
352
- "WARNING: Do not use periods in filenames. Rename: #{fancy_ticks(path.basename)}"
366
+ "WARNING: Do not use periods in filenames." +
367
+ " Rename: #{fancy_ticks(path.basename)}"
353
368
  )
354
369
  end
355
370
  path.dirname.ascend do |ancestor|
356
371
  break if ancestor == root
357
372
  if ancestor.basename.to_s.include?('.')
358
373
  warning(
359
- "WARNING: Do not use periods in directory names. Rename: #{fancy_ticks(ancestor.basename)}"
374
+ "WARNING: Do not use periods in directory names." +
375
+ " Rename: #{fancy_ticks(ancestor.basename)}"
360
376
  )
361
377
  end
362
378
  end
@@ -401,6 +417,24 @@ module MrMurano
401
417
  attr_accessor :phantom
402
418
  # @return [Boolean] True if a service that should not be deleted remotely.
403
419
  attr_accessor :undeletable
420
+
421
+ def reject_ephemeral!
422
+ super.reject do |attr_key, attr_val|
423
+ [
424
+ # Server-siders:
425
+ # :alias,
426
+ # :service,
427
+ # :event,
428
+ # :type,
429
+ :updated_at,
430
+ :created_at,
431
+ :solution_id,
432
+ :svc_alias,
433
+ :phantom,
434
+ :undeletable,
435
+ ].include?(attr_key) || ((attr_key == :type) && (attr_val.nil?))
436
+ end
437
+ end
404
438
  end
405
439
 
406
440
  def initialize(api_id=nil)
@@ -413,13 +447,15 @@ module MrMurano
413
447
  end
414
448
 
415
449
  def mkalias(remote)
416
- raise "Missing parts! #{remote.to_h.to_json}" if remote.service.nil? || remote.event.nil?
450
+ missing_parts = remote.service.nil? || remote.event.nil?
451
+ raise "Missing parts! #{remote.to_h.to_json}" if missing_parts
417
452
  #[$cfg[@solntype], remote[:service], remote[:event]].join('_')
418
453
  [@api_id, remote[:service], remote[:event]].join('_')
419
454
  end
420
455
 
421
456
  def mkname(remote)
422
- raise "Missing parts! #{remote.to_h.to_json}" if remote.service.nil? || remote.event.nil?
457
+ missing_parts = remote.service.nil? || remote.event.nil?
458
+ raise "Missing parts! #{remote.to_h.to_json}" if missing_parts
423
459
  [remote[:service], remote[:event]].join('_')
424
460
  end
425
461
 
@@ -449,10 +485,18 @@ module MrMurano
449
485
  # Substitute '{product.id}' for the actual product.id if a corresponding
450
486
  # local file does not exist, or if the local file already uses the alias.
451
487
  encode_items = {}
488
+ build_svc_alias_map_from_local(encode_items, local)
489
+ resolve_name_svc_alias!(encode_items, there)
490
+ end
491
+
492
+ def build_svc_alias_map_from_local(encode_items, local)
452
493
  local.each do |item|
453
494
  encode_items[item.service] = {} if encode_items[item.service].nil?
454
495
  encode_items[item.service][item.event] = item.svc_alias
455
496
  end
497
+ end
498
+
499
+ def resolve_name_svc_alias!(encode_items, there)
456
500
  there.map! do |item|
457
501
  if (
458
502
  !encode_items[item.service].nil? &&
@@ -536,21 +580,20 @@ module MrMurano
536
580
  "#{item[:name]}.lua"
537
581
  end
538
582
 
539
- def to_remote_item(from, path)
540
- # This allows multiple events to be in the same file. This is a lie.
541
- # This only finds the last event in a file.
542
- # :legacy support doesn't allow for that. But that's ok.
543
- path = Pathname.new(path) unless path.is_a?(Pathname)
583
+ def to_remote_items(from, path)
584
+ # Parses file which may contain multiple events. Return array of Items.
544
585
  items = []
545
586
  cur = nil
546
587
  lineno = 0
588
+ path = Pathname.new(path) unless path.is_a?(Pathname)
547
589
  path.readlines.each do |line|
548
590
  lineno += 1
549
591
  # @match_header finds a service and an event string, e.g., "--EVENT svc evt\n"
550
592
  md = @match_header.match(line)
551
593
  if !md.nil?
552
594
  cur[:line_end] = lineno - 1 unless cur.nil?
553
- cur = to_remote_item_create(md, path, line, lineno)
595
+ header = line.strip
596
+ cur = to_remote_item_create(md, path, header, lineno)
554
597
  items << cur
555
598
  elsif !cur.nil? && !cur[:script].nil?
556
599
  cur[:script] += line
@@ -563,22 +606,23 @@ module MrMurano
563
606
  else
564
607
  # If cur is nil here, then we need to do a :legacy check.
565
608
  cur = to_remote_item_legacy_check(cur, path, from, lineno)
566
- items << cur
609
+ items << cur unless cur.nil?
567
610
  end
568
611
 
569
612
  items
570
613
  end
571
614
 
572
- def to_remote_item_create(md, path, line, lineno)
615
+ def to_remote_item_create(md, path, header, lineno)
573
616
  service, config_var = decode_config_var(md[:service])
574
617
  event_event, event_type = resolve_event_type(service, md[:event])
575
- # Header line.
618
+ header = config_vars_decode(header)
576
619
  svc_alias = config_var if service != md[:service]
577
620
  EventHandlerItem.new(
578
- # Skip: name, id, line_end, diff, selected, synckey, synctype, type,
579
- # updated_at, dup_count, alias, updted_at, created_at, solution_id, phantom.
621
+ # Skipping: alias, updated_at, created_at, solution_id, phantom, undeletable
622
+ # name, id, line_end, diff, selected, synckey, synctype, updated_at, dup_count
580
623
  local_path: path,
581
- script: line,
624
+ header: header,
625
+ script: '',
582
626
  line_beg: lineno,
583
627
  service: service,
584
628
  event: event_event,
@@ -751,9 +795,10 @@ module MrMurano
751
795
  undeletable.updated_at = nil
752
796
  # Even if the user deletes the contents of a script,
753
797
  # the platform still sends the magic header.
754
- undeletable.script = (
755
- "--#EVENT #{therebox[key].service} #{therebox[key].event}\n"
798
+ undeletable.header = (
799
+ "--#EVENT #{therebox[key].service} #{therebox[key].event}"
756
800
  )
801
+ undeletable.script = ''
757
802
  undeletable.local_path = Pathname.new(
758
803
  File.join(location, tolocalname(thereitem, key))
759
804
  )
@@ -115,6 +115,7 @@ module MrMurano
115
115
 
116
116
  here.map! { |i| Hash.transform_keys_to_symbols(i) }
117
117
 
118
+ # (lb): FIXME?: To be like other localitems methods, should return Item list, not Hash.
118
119
  sort_by_name(here)
119
120
  end
120
121
  end
@@ -57,9 +57,16 @@ module MrMurano
57
57
 
58
58
  ##
59
59
  # Remove all syncables.
60
- def reset
61
- @syncset = []
62
- @synctypes = {}
60
+ def reset(syncsetypes=nil)
61
+ new_syncsetypes = [@syncset, @synctypes]
62
+ if syncsetypes.nil?
63
+ @syncset = []
64
+ @synctypes = {}
65
+ else
66
+ @syncset = syncsetypes[0]
67
+ @synctypes = syncsetypes[1]
68
+ end
69
+ new_syncsetypes
63
70
  end
64
71
 
65
72
  ##
@@ -51,21 +51,28 @@ module MrMurano
51
51
  tomod = dt[:tomod]
52
52
 
53
53
  itemkey = @itemkey.to_sym
54
- todel.each do |item|
54
+
55
+ todel.reject { |item| item[:phantom] }.each do |item|
55
56
  syncup_item(item, options, :delete, 'Removing') do |aitem|
56
- remove_lite(aitem[itemkey], aitem.reject { |k, _v| k == :local_path }, true)
57
+ remove(aitem[itemkey])
58
+ num_synced += 1
59
+ end
60
+ end
61
+ todel.select { |item| item[:phantom] }.each do |item|
62
+ syncup_item(item, options, :delete, 'Clearing') do |aitem|
63
+ remove_or_clear(aitem[itemkey], aitem.reject_ephemeral!, true)
57
64
  num_synced += 1
58
65
  end
59
66
  end
60
67
  toadd.each do |item|
61
68
  syncup_item(item, options, :create, 'Adding') do |aitem|
62
- upload(aitem[:local_path], aitem.reject { |k, _v| k == :local_path }, false)
69
+ upload(aitem[:local_path], aitem.reject_ephemeral!, false)
63
70
  num_synced += 1
64
71
  end
65
72
  end
66
73
  tomod.each do |item|
67
74
  syncup_item(item, options, :update, 'Updating') do |aitem|
68
- upload(aitem[:local_path], aitem.reject { |k, _v| k == :local_path }, true)
75
+ upload(aitem[:local_path], aitem.reject_ephemeral!, true)
69
76
  num_synced += 1
70
77
  end
71
78
  end
@@ -163,7 +170,7 @@ module MrMurano
163
170
  def dodiff(merged, local, _there=nil, options={})
164
171
  localname = dodiff_resolve_localname(merged)
165
172
  trmt, tlcl = dodiff_tempfile_paths(localname)
166
- dodiff_header_aware(merged, trmt, tlcl, options)
173
+ dodiff_header_aware(merged, local, trmt, tlcl, options)
167
174
  end
168
175
 
169
176
  def dodiff_resolve_localname(merged)
@@ -186,10 +193,10 @@ module MrMurano
186
193
  raise
187
194
  end
188
195
 
189
- def dodiff_header_aware(merged, trmt, tlcl, options)
196
+ def dodiff_header_aware(merged, local, trmt, tlcl, options)
190
197
  dodiff_download_remote(merged, trmt, options)
191
198
  MrMurano::Verbose.whirly_stop
192
- dodiff_prepare_local_and_diff(merged, trmt, tlcl, options)
199
+ dodiff_prepare_local_and_diff(merged, local, trmt, tlcl, options)
193
200
  ensure
194
201
  trmt.close
195
202
  trmt.unlink
@@ -202,13 +209,9 @@ module MrMurano
202
209
  diff_download(tmp_path, merged, options)
203
210
  end
204
211
 
205
- def dodiff_prepare_local_and_diff(merged, trmt, tlcl, options)
212
+ def dodiff_prepare_local_and_diff(merged, local, trmt, tlcl, options)
206
213
  cmd = dodiff_build_cmd(trmt, tlcl, options)
207
- merged[:exclude_header] = true
208
- outp = dodiff_flexible(merged, tlcl, local, cmd, use_header=false)
209
- return outp if outp.to_s.empty? || !merged.key?(:script)
210
- merged[:exclude_header] = false
211
- dodiff_diff_dynamic(merged, tlcl, local, cmd, use_header=true)
214
+ dodiff_flexible(merged, local, tlcl, cmd, use_header: true)
212
215
  end
213
216
 
214
217
  def dodiff_build_cmd(trmt, tlcl, options)
@@ -232,18 +235,22 @@ module MrMurano
232
235
  cmd
233
236
  end
234
237
 
235
- def dodiff_flexible(merged, tlcl, local, cmd, use_header)
236
- dodiff_local_to_tempfile(merged, tlcl, local, use_header)
238
+ def dodiff_flexible(merged, local, tlcl, cmd, use_header: true)
239
+ dodiff_local_to_tempfile(merged, local, tlcl, use_header: use_header)
237
240
  dodiff_do_diff(cmd)
238
241
  end
239
242
 
240
- def dodiff_local_to_tempfile(merged, tlcl, local, use_header)
243
+ def dodiff_local_to_tempfile(merged, local, tlcl, use_header: true)
241
244
  Pathname.new(tlcl.path).open('wb') do |io|
242
245
  # Copy the local file to a temp file, for the diff command.
243
246
  # And for resources, remove the local-only :selected key, as
244
247
  # it's not part of the remote item that gets downloaded next.
245
248
  if merged.key?(:script)
246
- io << config_vars_decode(merged[:header]) if use_header
249
+ if use_header && !merged[:header].to_s.empty?
250
+ io << config_vars_decode(merged[:header])
251
+ # Add newline after the :header.
252
+ io.puts
253
+ end
247
254
  io << config_vars_decode(merged[:script])
248
255
  else
249
256
  # For most items, read the local file.
@@ -379,17 +386,7 @@ module MrMurano
379
386
 
380
387
  items_cull_clashes!(statuses)
381
388
 
382
- # 2018-04-24: (lb): How did I not see this til now? :unselected is :notimplemented.
383
- unless options[:unselected]
384
- statuses.each do |bucket, items|
385
- select_selected(items)
386
- next unless $cfg['tool.debug']
387
- loci = items.map { |item| item.location_friendly(full_path: true) }
388
- selected_paths = loci.sort.join("\n ")
389
- selected_paths = (selected_paths.empty? && ' <none>' || "\n ") + selected_paths
390
- debug %(#{self.class}: Selected #{bucket} items:#{selected_paths})
391
- end
392
- end
389
+ item_select_selected!(statuses, options)
393
390
 
394
391
  statuses
395
392
  end
@@ -631,14 +628,6 @@ module MrMurano
631
628
  end
632
629
  end
633
630
 
634
- def select_selected(items)
635
- items.select! { |item| item[:selected] }
636
- items.map do |item|
637
- item.delete(:selected)
638
- item
639
- end
640
- end
641
-
642
631
  def items_cull_clashes!(statuses)
643
632
  clash = []
644
633
  statuses.each_value do |items|
@@ -656,6 +645,28 @@ module MrMurano
656
645
  end
657
646
  statuses[:clash] = clash
658
647
  end
648
+
649
+ def item_select_selected!(statuses, options)
650
+ # (lb): :unselected is currently only used by tests.
651
+ return if options[:unselected]
652
+ statuses.each do |bucket, items|
653
+ select_selected!(items)
654
+ debug_selected(bucket, items)
655
+ end
656
+ end
657
+
658
+ def select_selected!(items)
659
+ items.select! { |item| item[:selected] }
660
+ items.map { |item| item.delete(:selected) }
661
+ end
662
+
663
+ def debug_selected(bucket, items)
664
+ return unless $cfg['tool.debug']
665
+ loci = items.map { |item| item.location_friendly(full_path: true) }
666
+ selected_paths = loci.sort.join("\n ")
667
+ selected_paths = (selected_paths.empty? && ' <none>' || "\n ") + selected_paths
668
+ debug %(#{self.class}: Selected #{bucket} items:#{selected_paths})
669
+ end
659
670
  end
660
671
  end
661
672