squared 0.6.15 → 0.7.0

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.
@@ -20,7 +20,7 @@ module Squared
20
20
  sbom=q].freeze
21
21
  }.freeze,
22
22
  compose: {
23
- common: %w[all-resources ansi=b compatibility dry-run env-file=p f|file=p parallel=n profile=b progress=b
23
+ common: %w[all-resources ansi|b compatibility dry-run env-file=p f|file=p parallel=n profile=b progress=b
24
24
  project-directory=p p|project-name=e].freeze,
25
25
  build: %w[check no-cache print pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b
26
26
  provenance=q sbom=q ssh=qq].freeze,
@@ -33,7 +33,8 @@ module Squared
33
33
  d|detach force-recreate menu no-build no-color no-deps no-log-prefix no-recreate no-start quiet-build
34
34
  quiet-pull remove-orphans V|renew-anon-volumes timestamps wait w|watch y|yes attach=b
35
35
  exit-code-from=b no-attach=b pull=b scale=i t|timeout=i wait-timeout=i].freeze,
36
- down: %w[remove-orphans v|volumes rmi=b t|timeout=i].freeze
36
+ down: %w[remove-orphans v|volumes rmi=b t|timeout=i].freeze,
37
+ publish: %w[app resolve-image-digests with-env y|yes oci-version=b].freeze
37
38
  }.freeze,
38
39
  container: {
39
40
  create: %w[init i|interactive no-healthcheck oom-kill-disable privileged P|publish-all q|quiet read-only
@@ -64,6 +65,7 @@ module Squared
64
65
  }.freeze,
65
66
  image: {
66
67
  ls: %w[a|all digests no-trunc q|quiet tree f|filter=q format=q].freeze,
68
+ pull: %w[a|all-tags platform=q q|quiet].freeze,
67
69
  push: %w[a|all-tags platform=q q|quiet].freeze,
68
70
  rm: %w[f|force no-prune platform=q].freeze,
69
71
  save: %w[o|output=p platform=q].freeze
@@ -107,9 +109,9 @@ module Squared
107
109
 
108
110
  subtasks({
109
111
  'build' => %i[tag context].freeze,
110
- 'compose' => %i[build create run exec up down service].freeze,
111
- 'bake' => %i[build check].freeze,
112
- 'image' => %i[ls rm push tag save].freeze,
112
+ 'compose' => %i[build create publish run exec up down service].freeze,
113
+ 'bake' => %i[build compose check].freeze,
114
+ 'image' => %i[ls rm pull push tag save].freeze,
113
115
  'container' => %i[run create exec update commit inspect diff start stop restart pause unpause top stats kill
114
116
  rm].freeze,
115
117
  'network' => %i[connect disconnect].freeze,
@@ -119,17 +121,19 @@ module Squared
119
121
  attr_reader :context
120
122
  attr_accessor :tag
121
123
 
122
- def initialize(*, file: nil, context: nil, tag: nil, secrets: nil, mounts: [], registry: nil, **kwargs)
124
+ def initialize(*, mounts: [], **kwargs)
123
125
  super
124
- return unless dockerfile(file).exist?
126
+ self.global = nil
127
+ return unless dockerfile(kwargs[:file]).exist?
125
128
 
126
- @context = context
127
- self.tag = tag || tagname("#{@project}:#{@version || 'latest'}")
129
+ self.tag = kwargs[:tag] || tagname("#{@project}:#{@version || 'latest'}")
130
+ @context = kwargs[:context]
128
131
  @mounts = mounts
129
- @secrets = secrets
130
- @registry = tagjoin registry, kwargs[:username]
132
+ @secrets = kwargs[:secrets]
133
+ @registry = tagjoin kwargs[:registry], kwargs[:username]
134
+ @oci = tagjoin kwargs[:oci], kwargs[:username]
131
135
  initialize_ref Docker.ref
132
- initialize_logger(**kwargs)
136
+ initialize_logger kwargs[:log]
133
137
  initialize_env(**kwargs)
134
138
  @output[4] = merge_opts(kwargs[:args], @output[4]) if kwargs[:args]
135
139
  end
@@ -163,25 +167,21 @@ module Squared
163
167
  end
164
168
  cmd << '-a' if has_value!(args, 'a', 'all') && command != 'network'
165
169
  data = VAL_DOCKER[:ls][command.to_sym]
166
- cols = if has_value!(args, 's', 'standard')
167
- data.first(data.index('CreatedAt'))
168
- else
169
- [].tap do |out|
170
- args.each do |val|
171
- if val =~ /^(\d+)$/
172
- out << data[$1.to_i.pred]
173
- elsif val =~ /^(\d+)(-|\.{2,3})(\d+)$/
174
- j = $1.to_i.pred
175
- k = $3.to_i - ($2 == '..' ? 2 : 1)
176
- out.concat(data[j..k]) if k > j
177
- end
178
- end
179
- next unless out.empty?
180
-
181
- out.replace(choice_index('Select a column', data, multiple: true, attempts: 1))
182
- end
183
- end
184
- cmd << quote_option('format', "table #{cols.map! { |val| "{{.#{val}}}" }.join("\t")}")
170
+ if has_value!(args, 's', 'standard')
171
+ cols = data.first(data.index('CreatedAt'))
172
+ else
173
+ cols = args.each_with_object([]) do |val, out|
174
+ if val =~ /^(\d+)$/
175
+ out << data[$1.to_i.pred]
176
+ elsif val =~ /^(\d+)(-|\.{2,3})(\d+)$/
177
+ j = $1.to_i.pred
178
+ k = $3.to_i - ($2 == '..' ? 2 : 1)
179
+ out.concat(data[j..k]) if k > j
180
+ end
181
+ end
182
+ cols = choice_index('Select a column', data, multiple: true, attempts: 1) if cols.empty?
183
+ end
184
+ cmd << quote_option('format', "table #{cols.map { |val| "{{.#{val}}}" }.join("\t")}")
185
185
  run(cmd, banner: false, from: :ls)
186
186
  end
187
187
  end
@@ -199,14 +199,14 @@ module Squared
199
199
  break unless bake?
200
200
 
201
201
  case flag
202
- when :build
203
- format_desc action, flag, 'opts*,target*,context|:'
202
+ when :build, :compose
203
+ format_desc action, flag, 'opts*,target*,context?|:'
204
204
  task flag do |_, args|
205
205
  args = args.to_a
206
206
  if args.first == ':'
207
207
  choice_command :bake
208
208
  else
209
- buildx :bake, args
209
+ buildx(:bake, args, from: (:'buildx:bake' if flag == :compose))
210
210
  end
211
211
  end
212
212
  when :check
@@ -239,6 +239,13 @@ module Squared
239
239
  compose!(flag, [command], service: service.empty? || service)
240
240
  end
241
241
  end
242
+ when :publish
243
+ next unless @oci
244
+
245
+ format_desc action, flag, 'tag?,repository?,opts*'
246
+ task flag, [:tag] do |_, args|
247
+ compose! flag, args.to_a
248
+ end
242
249
  else
243
250
  format_desc action, flag, 'opts*,service*|:'
244
251
  task flag do |_, args|
@@ -275,6 +282,13 @@ module Squared
275
282
  when 'image'
276
283
  case flag
277
284
  when :push
285
+ next unless @registry
286
+
287
+ format_desc action, flag, 'tag?,registry/username?,opts*'
288
+ task flag, [:tag] do |_, args|
289
+ image flag, args.to_a
290
+ end
291
+ when :pull
278
292
  format_desc action, flag, 'tag,registry/username?,opts*'
279
293
  task flag, [:tag] do |_, args|
280
294
  id = param_guard(action, flag, args: args, key: :tag)
@@ -282,18 +296,14 @@ module Squared
282
296
  end
283
297
  else
284
298
  format_desc(action, flag, case flag
285
- when :rm, :save then 'id,opts*'
286
- when :tag then 'version'
299
+ when :rm, :save then 'id*,opts*'
300
+ when :tag then 'version?'
287
301
  else 'opts*,args*'
288
- end, before: 'pattern?')
302
+ end)
289
303
  task flag do |_, args|
290
304
  args = args.to_a
291
- n = args.size
292
- if (n > 1 || (flag == :ls && n > 0)) && OptionPartition.pattern?(args.first)
293
- filter = args.shift
294
- end
295
305
  if !args.empty? || flag == :ls
296
- image(flag, args, filter: filter)
306
+ image flag, args
297
307
  else
298
308
  choice_command flag
299
309
  end
@@ -319,8 +329,8 @@ module Squared
319
329
  def clean(*, sync: invoked_sync?('clean'), **)
320
330
  if runnable?(@clean)
321
331
  super
322
- elsif sync || option('y', prefix: 'docker')
323
- image :rm
332
+ else
333
+ image(:rm, sync: sync)
324
334
  end
325
335
  end
326
336
 
@@ -353,7 +363,7 @@ module Squared
353
363
  [args, flags].each_with_index do |item, i|
354
364
  next unless item && (data = append_any(item, target: []))
355
365
 
356
- ret.merge(data.map! { |arg| i == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
366
+ ret.merge(data.map { |arg| i == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
357
367
  end
358
368
  case from
359
369
  when :run
@@ -361,13 +371,13 @@ module Squared
361
371
  when String
362
372
  ret << quote_option('secret', @secrets, double: true)
363
373
  when Hash
364
- append = lambda do |type|
365
- Array(@secrets[type]).each { |arg| ret << quote_option('secret', "type=#{type},#{arg}", double: true) }
374
+ append = lambda do |key|
375
+ ret.merge(Array(@secrets[key]).map { |arg| quote_option('secret', "type=#{key},#{arg}", double: true) })
366
376
  end
367
377
  append.call(:file)
368
378
  append.call(:env)
369
379
  else
370
- Array(@secrets).each { |arg| ret << quote_option('secret', arg) }
380
+ ret.merge(Array(@secrets).map { |arg| quote_option('secret', arg) })
371
381
  end
372
382
  if (val = option('tag', ignore: false))
373
383
  append_tag val
@@ -381,36 +391,38 @@ module Squared
381
391
  ret
382
392
  end
383
393
 
384
- def buildx(flag, opts = [], tag: nil, context: nil)
394
+ def buildx(flag, opts = [], tag: nil, context: nil, from: nil)
385
395
  cmd, opts = docker_session('buildx', opts: opts)
386
- op = OPT_DOCKER[:buildx].yield_self do |data|
387
- OptionPartition.new(opts, data[:common], cmd, project: self)
388
- .append(flag, quote: false)
389
- .parse(data[flag == :bake ? :bake : :build] + data[:shared])
390
- end
396
+ data = OPT_DOCKER[:buildx]
397
+ op = OptionPartition.new(opts, data[:common], cmd, project: self)
398
+ op.append(flag, quote: false)
399
+ .parse(data[flag == :bake ? :bake : :build] + data[:shared])
391
400
  case flag
392
401
  when :build, :context
393
402
  append_tag(tag || option('tag', ignore: false) || self.tag)
394
403
  append_context context
395
404
  when :bake
405
+ append_file(0, index: 3) unless from || op.arg?('f', 'file') || !anypath?(*COMPOSEFILE)
396
406
  unless op.empty?
397
- if !context && op.size > 1 && basepath(op.last).directory?
398
- pat = /\btarget\s+"#{Regexp.escape(op.last)}"/
399
- context = op.pop if op.values_of('f', 'file').none? { |f| basepath!(f)&.read&.match?(pat) }
400
- end
401
- if context
402
- context = basepath context
403
- op.each { |name| op.add_option('set', "#{name}.context=#{context}", escape: false) }
407
+ args = op.dup
408
+ op.reset
409
+ if Dir.exist?(args.last)
410
+ if projectpath?(val = args.pop)
411
+ context = val
412
+ else
413
+ op.push(val)
414
+ end
404
415
  end
405
- op.append(escape: true, strip: /^:/, clear: true)
416
+ op.append(args, escape: true, strip: /^:/)
417
+ contextdir context if context
406
418
  end
407
419
  end
408
420
  op.clear(pass: false)
409
- run(from: :"buildx:#{flag}")
421
+ run(from: from || symjoin('buildx', flag))
410
422
  end
411
423
 
412
- def compose!(flag, opts = [], service: nil, multiple: false)
413
- from = :"compose:#{flag}"
424
+ def compose!(flag, opts = [], id: nil, service: nil, multiple: false)
425
+ from = symjoin 'compose', flag
414
426
  if flag == :service
415
427
  command = opts.first
416
428
  if service == true
@@ -424,60 +436,64 @@ module Squared
424
436
  else
425
437
  cmd, opts = docker_session('compose', opts: opts)
426
438
  op = OptionPartition.new(opts, OPT_DOCKER[:compose][:common], cmd, project: self)
427
- append_file filetype unless op.arg?('f', 'file')
439
+ append_file(filetype, force: flag == :publish) unless op.arg?('f', 'file')
428
440
  op << flag
429
441
  op.parse(OPT_DOCKER[:compose].fetch(flag, []))
430
- if op.remove(':') || service == ':'
431
- keys = Set.new
432
- read_composefile('services', target: op.values_of('f', 'file')) { |data| keys.merge(data.keys) }
433
- service = unless keys.empty?
434
- choice_index('Add services', keys, multiple: multiple, force: !multiple,
435
- attempts: multiple ? 1 : 3)
436
- end
437
- end
438
- if multiple
439
- op.concat(service) if service
440
- op.append(delim: true, escape: true, strip: /^:/)
442
+ if flag == :publish
443
+ id ||= option('tag', ignore: false) || op.shift || tagmain
444
+ registry ||= option('registry') || op.shift || @oci
445
+ emptyargs op, flag
446
+ op << shell_quote(tagjoin(registry, id))
447
+ return unless confirm_command(op.to_s, title: from, target: id)
441
448
  else
442
- raise_error ArgumentError, 'no service was selected', hint: flag unless service
443
- append_command(flag, service, op.extras, prompt: '::')
449
+ if op.remove(':') || service == ':'
450
+ keys = Set.new
451
+ read_composefile('services', target: op.values_of('f', 'file')) { |data| keys.merge(data.keys) }
452
+ service = unless keys.empty?
453
+ choice_index('Add services', keys, multiple: multiple, force: !multiple,
454
+ attempts: multiple ? 1 : 3)
455
+ end
456
+ end
457
+ if multiple
458
+ op.concat(service) if service
459
+ op.append(delim: true, escape: true, strip: /^:/)
460
+ else
461
+ raise_error ArgumentError, 'no service was selected', hint: flag unless service
462
+ append_command(flag, service, op.extras, prompt: '::')
463
+ end
444
464
  end
445
465
  end
446
466
  run(from: from)
447
467
  end
448
- alias compose_ compose!
449
468
 
450
469
  def container(flag, opts = [], id: nil)
451
470
  cmd, opts = docker_session('container', flag, opts: opts)
452
- rc = flag == :run || flag == :create
453
- op = OPT_DOCKER[:container].yield_self do |data|
454
- list = data.fetch(flag, [])
455
- list += data[:create] if flag == :run
456
- list += data[:update] if rc
457
- OptionPartition.new(opts, list, cmd, project: self, args: rc || flag == :exec)
458
- end
459
- from = :"container:#{flag}"
471
+ data = OPT_DOCKER[:container]
472
+ list = data.fetch(flag, [])
473
+ list += data[:create] if (rc = flag == :run)
474
+ list += data[:update] if rc ||= flag == :create
475
+ op = OptionPartition.new(opts, list, cmd, project: self, args: rc || flag == :exec)
476
+ from = symjoin 'container', flag
460
477
  case flag
461
478
  when :run, :create, :exec
462
479
  if rc && !op.arg?('mount')
463
480
  all = collect_hash VAL_DOCKER[:run]
464
481
  delim = Regexp.new(",\\s*(?=#{all.join('|')})")
465
482
  Array(@mounts).each do |val|
466
- args = []
467
483
  type = nil
468
- val.split(delim).each do |opt|
469
- k, v, q = split_option opt
484
+ args = val.split(delim).each_with_object([]) do |opt, out|
485
+ k, v, q = OptionPartition.parse_option(opt)
470
486
  if k == 'type'
471
487
  case v
472
488
  when 'bind', 'volume', 'image', 'tmpfs'
473
489
  type = v
474
490
  else
475
- raise_error TypeError, "unknown: #{v}", hint: flag
491
+ raise_error TypeError, "unknown: #{v || "''"}", hint: flag
476
492
  end
477
493
  elsif all.include?(k)
478
494
  unless type
479
- VAL_DOCKER[:run].each_pair do |key, a|
480
- next unless a.include?(k)
495
+ VAL_DOCKER[:run].each_pair do |key, items|
496
+ next unless items.include?(k)
481
497
 
482
498
  type = key.to_s unless key == :common
483
499
  break
@@ -485,13 +501,14 @@ module Squared
485
501
  end
486
502
  case k
487
503
  when 'readonly', 'ro'
488
- args << k
504
+ out << k
489
505
  next
490
506
  when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-path'
507
+ raise_error ArgumentError, "#{k}: no path value", hint: flag unless v
491
508
  v = basepath v
492
509
  v = shell_quote(v, option: false, force: false) if q == ''
493
510
  end
494
- args << "#{k}=#{q + v + q}"
511
+ out << "#{k}=#{q}#{v}#{q}"
495
512
  elsif !silent?
496
513
  log_message('unrecognized option', subject: from, hint: k)
497
514
  end
@@ -517,7 +534,7 @@ module Squared
517
534
  opts = []
518
535
  append_option('platform', target: opts, equals: true)
519
536
  opts << case option('disable-content-trust', ignore: false)
520
- when 'false', '0'
537
+ when '0', 'false'
521
538
  '--disable-content-trust=false'
522
539
  else
523
540
  '--disable-content-trust'
@@ -536,12 +553,12 @@ module Squared
536
553
  run(from: from)
537
554
  end
538
555
 
539
- def image(flag, opts = [], sync: true, id: nil, registry: nil, filter: nil)
556
+ def image(flag, opts = [], sync: true, id: nil, registry: nil)
540
557
  cmd, opts = docker_session('image', flag, opts: opts)
541
558
  op = OptionPartition.new(opts, OPT_DOCKER[:image].fetch(flag, []), cmd, project: self)
542
- exception = self.exception
559
+ exception = exception?
543
560
  banner = true
544
- from = :"image:#{flag}"
561
+ from = symjoin 'image', flag
545
562
  case flag
546
563
  when :ls
547
564
  if opts.size == op.size
@@ -554,7 +571,7 @@ module Squared
554
571
  opts.delete(val)
555
572
  break
556
573
  end
557
- list_image(:run, filter: filter, from: from) do |val|
574
+ list_image(:run, from: from) do |val|
558
575
  container(:run, if name
559
576
  opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
560
577
  else
@@ -568,7 +585,7 @@ module Squared
568
585
  when :rm
569
586
  unless id
570
587
  if op.empty?
571
- list_image(:rm, filter: filter, from: from) { |val| image(:rm, opts, sync: sync, id: val) }
588
+ list_image(:rm, from: from) { |val| image(:rm, opts, sync: sync, id: val) }
572
589
  else
573
590
  op.each { |val| run(cmd.temp(val), sync: sync, from: from) }
574
591
  end
@@ -580,51 +597,48 @@ module Squared
580
597
  banner = false
581
598
  end
582
599
  when :tag, :save
583
- found = false
584
- list_image(flag, filter: filter, from: from) do |val|
600
+ list_image(flag, from: from) do |val|
585
601
  op << val
586
- found = true
587
- if flag == :tag
588
- op << tagname("#{project}:#{op.first}")
589
- break
590
- end
602
+ next unless flag == :tag
603
+
604
+ op << tagname("#{project}:#{op.first}")
605
+ break
591
606
  end
592
- raise_error ArgumentError, 'target not specified', hint: flag unless found
593
- when :push
594
- id ||= option('tag', ignore: false) || tagmain
595
- registry ||= op.shift || option('registry') || @registry
596
- unless id && op.empty?
597
- if id
598
- raise_error ArgumentError, "unrecognized args: #{op.join(', ')}", hint: flag
599
- else
600
- raise_error 'no id/tag', hint: flag
601
- end
607
+ when :pull
608
+ if !id
609
+ id = tagmain
610
+ elsif !op.arg?('a', 'all-tags') && !id.include?(':')
611
+ id = "#{project}:#{id}"
602
612
  end
613
+ unless registry
614
+ registry = op.shift
615
+ registry ||= option('registry') || @registry unless id.include?('/')
616
+ end
617
+ cmd << shell_quote(tagjoin(registry, id))
618
+ when :push
619
+ id ||= option('tag', ignore: false) || op.shift || tagmain
620
+ registry ||= option('registry') || op.shift || @registry
621
+ emptyargs op, flag
603
622
  raise_error ArgumentError, 'username/registry not specified', hint: flag unless registry
604
- registry.chomp!('/')
605
- uri = shell_quote "#{registry}/#{id}"
623
+ uri = shell_quote tagjoin(registry, id)
606
624
  op << uri
607
625
  img = docker_output 'image', 'tag', id, uri
608
626
  return unless confirm_command(img.to_s, cmd.to_s, target: id, as: registry, title: from)
609
627
 
610
- cmd = img
628
+ @session = img
611
629
  sync = false
612
- exception = true
630
+ exception ||= true
613
631
  banner = false
614
632
  end
615
- run(cmd, sync: sync, exception: exception, banner: banner, from: from).tap do |ret|
616
- success?(ret, flag == :tag || flag == :save)
617
- end
633
+ success?(run(sync: sync, exception: exception, banner: banner, from: from), flag == :tag || flag == :save)
618
634
  end
619
635
 
620
636
  def network(flag, opts = [], target: nil)
621
637
  cmd, opts = docker_session('network', flag, opts: opts)
622
638
  OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self)
623
639
  .clear
624
- from = :"network:#{flag}"
625
- list_image(flag, docker_output('ps -a'), from: from) do |img|
626
- success?(run(cmd.temp(target, img), from: from))
627
- end
640
+ from = symjoin 'network', flag
641
+ list_image(flag, docker_output('ps -a'), from: from) { |id| success?(run(cmd.temp(target, id), from: from)) }
628
642
  end
629
643
 
630
644
  def build?
@@ -673,7 +687,7 @@ module Squared
673
687
  elsif (data = doc.dig(*keys))
674
688
  yield data
675
689
  end
676
- rescue StandardError => e
690
+ rescue => e
677
691
  log.debug e
678
692
  end
679
693
  end
@@ -690,11 +704,11 @@ module Squared
690
704
  end
691
705
 
692
706
  def append_command(flag, val, list, target: @session, prompt: ':')
693
- if list.delete(prompt)
694
- list << readline('Enter command [args]', force: flag == :exec)
695
- else
696
- env('DOCKER_ARGS') { |args| list << args }
697
- end
707
+ list << if list.delete(prompt)
708
+ readline('Enter command [args]', force: flag == :exec)
709
+ else
710
+ env('DOCKER_ARGS')
711
+ end
698
712
  case flag
699
713
  when :run
700
714
  unless session_arg?('name', target: target)
@@ -707,10 +721,10 @@ module Squared
707
721
  target << list.join(' && ') unless list.empty?
708
722
  end
709
723
 
710
- def append_file(type, target: @session, index: 2)
711
- return if !@file || (ENV['COMPOSE_FILE'] && compose?(type))
724
+ def append_file(type, target: @session, index: 2, force: false)
725
+ return unless @file && !(ENV['COMPOSE_FILE'] && compose?(type))
712
726
 
713
- unless @file.is_a?(Array)
727
+ unless @file.is_a?(Array) || force
714
728
  case type
715
729
  when 2, 4
716
730
  return
@@ -718,14 +732,7 @@ module Squared
718
732
  return unless COMPOSEFILE.select { |val| basepath!(val) }.size > 1
719
733
  end
720
734
  end
721
- files = Array(@file).map { |val| quote_option('file', basepath(val)) }
722
- if target.is_a?(Set)
723
- opts = target.to_a.insert(index, *files)
724
- target.clear
725
- .merge(opts)
726
- else
727
- target.insert(index, *files)
728
- end
735
+ target.insert(index, *Array(@file).map { |val| quote_option('file', basepath(val)) })
729
736
  end
730
737
 
731
738
  def append_context(ctx = nil, target: @session)
@@ -739,7 +746,7 @@ module Squared
739
746
  ver = option('version', target: target, ignore: false)
740
747
  case val
741
748
  when String
742
- split_escape val
749
+ val.split(',')
743
750
  else
744
751
  Array(val)
745
752
  end.each do |s|
@@ -778,20 +785,12 @@ module Squared
778
785
  [cmd, status, no]
779
786
  end
780
787
 
781
- def list_image(flag, cmd = docker_output('image ls -a'), filter: nil, hint: nil, no: true, from: nil)
788
+ def list_image(flag, cmd = docker_output('image ls -a'), hint: nil, no: true, from: nil)
782
789
  pwd_set(from: from) do
783
790
  index = 1
784
791
  all = option('all', prefix: 'docker')
785
792
  y = from == :'image:rm' && option('y', prefix: 'docker')
786
- filter = env('DOCKER_FILTER', filter).to_s
787
- pat = if OptionPartition.pattern?(filter)
788
- Regexp.new(filter)
789
- elsif filter.match?(/[:_-]$/)
790
- /\b#{Regexp.escape(filter)}/
791
- else
792
- filter = filter.empty? ? '(?:[:_-]|$)' : "[:_-]#{filter}"
793
- /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})#{filter}/
794
- end
793
+ pat = /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})\b/
795
794
  IO.popen(cmd.temp('--format=json')).each do |line|
796
795
  data = JSON.parse(line)
797
796
  id = data['ID']
@@ -840,7 +839,7 @@ module Squared
840
839
  end
841
840
  list_empty(hint: hint || from) if index == 1 && !y
842
841
  end
843
- rescue StandardError => e
842
+ rescue => e
844
843
  on_error e, from
845
844
  end
846
845
 
@@ -874,11 +873,11 @@ module Squared
874
873
  when :service
875
874
  ['Choose a service',
876
875
  'compose ps -a ' \
877
- '--format="table {{.Service}}\t{{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Status}}\t{{.Ports}}"']
876
+ "--format='table {{.Service}}\t{{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Status}}\t{{.Ports}}'"]
878
877
  else
879
878
  ['Choose an image',
880
879
  'images -a ' \
881
- '--format="table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.CreatedSince}}\t{{.Size}}"']
880
+ "--format='table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.CreatedSince}}\t{{.Size}}'"]
882
881
  end
883
882
  lines = `#{docker_output(cmd)}`.lines
884
883
  if lines.size <= 1
@@ -912,18 +911,18 @@ module Squared
912
911
  when :tag
913
912
  args = tagjoin @registry, tag
914
913
  when :save
915
- opts = "#{opts}.tar" unless opts.end_with?('.tar')
914
+ opts = opts.sub_ext('.tar')
916
915
  cmd << quote_option('output', File.expand_path(opts))
917
916
  if args
918
- cmd << basic_option('platform', args)
917
+ cmd << quote_option('platform', args)
919
918
  args = nil
920
919
  end
921
920
  else
922
921
  cmd << opts << '--'
923
922
  end
924
- cmd.merge(Array(out).map! { |val| val.split(/\s+/, 2).first })
923
+ cmd.merge(Array(out).map { |val| val.split(/\s+/, 2).first })
925
924
  cmd << args
926
- success?(run(cmd), ctx.start_with?(/network|tag|save/))
925
+ success?(run(cmd), ctx.start_with?('network', 'tag', 'save'))
927
926
  end
928
927
 
929
928
  def filetype(val = dockerfile)
@@ -946,11 +945,12 @@ module Squared
946
945
  end
947
946
 
948
947
  def tagjoin(*args, char: '/')
949
- args.join(char) unless (args = args.compact).empty?
948
+ args.compact!
949
+ args.map { |val| val.chomp(char) }.join(char) unless args.empty?
950
950
  end
951
951
 
952
952
  def tagname(val)
953
- val = val.split(':').map! { |s| charname(s.sub(/^\W+/, '')) }
953
+ val = val.split(':').map { |s| charname(s.sub(/^\W+/, '')) }
954
954
  ret = val.join(':')
955
955
  ret = val.first if val.size > 1 && ret.size > 128
956
956
  ret[0..127]
@@ -967,6 +967,14 @@ module Squared
967
967
  def tagmain
968
968
  tag.is_a?(Array) ? tag.first : tag
969
969
  end
970
+
971
+ def emptyargs(list, hint = nil)
972
+ raise_error ArgumentError, "unrecognized args: #{list.join(', ')}", hint: hint unless list.empty?
973
+ end
974
+
975
+ def anypath?(*args)
976
+ args.any? { |val| basepath!(val) }
977
+ end
970
978
  end
971
979
 
972
980
  Application.implement Docker