squared 0.8.6 → 0.9.0.pre2

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.
@@ -102,7 +102,7 @@ module Squared
102
102
 
103
103
  @@tasks = {}
104
104
  @@graph = { _: [] }
105
- @@asdf = Pathname.new(ENV['ASDF_DIR'] || "#{Dir.home}/.asdf").yield_self do |path|
105
+ @@asdf = Pathname.new(ENV['ASDF_DIR'] || "#{Dir.home}/.asdf").then do |path|
106
106
  version = if path.join('asdf.sh').exist?
107
107
  15
108
108
  elsif ENV['ASDF_DATA_DIR'] && (path = Pathname.new(ENV['ASDF_DATA_DIR'])).exist?
@@ -640,7 +640,7 @@ module Squared
640
640
  end
641
641
  env('GRAPH', suffix: 'PASS') { |val| pass.concat(split_escape(val)) }
642
642
  start, neg = start.partition { |name| !name.start_with?('-') }
643
- data = graph_collect(self, start, pass: neg.map { |name| name[1..-1] })
643
+ data = graph_collect(self, start, pass: neg.map { |name| name[1..] })
644
644
  unless out
645
645
  data[name] << self
646
646
  on :first, :graph
@@ -700,7 +700,7 @@ module Squared
700
700
  headers = headers.is_a?(Hash) ? headers.merge(data) : data
701
701
  end
702
702
  if file
703
- ext ||= File.extname(file)[1..-1]
703
+ ext ||= File.extname(file)[1..]
704
704
  else
705
705
  require 'open-uri'
706
706
  data = nil
@@ -876,7 +876,7 @@ module Squared
876
876
  return
877
877
  end
878
878
  cmd = cmd.target if cmd.is_a?(OptionPartition)
879
- if interactive && sync && (!@session || !option('y'))
879
+ if interactive && sync && !(@session && option('y'))
880
880
  print_item(series: true)
881
881
  msg, y, h = case interactive
882
882
  when Array
@@ -924,9 +924,9 @@ module Squared
924
924
  end
925
925
  end
926
926
 
927
- def scope(*args, **kwargs, &blk)
927
+ def scope(...)
928
928
  namespace name do
929
- task(*args, **kwargs, &blk)
929
+ task(...)
930
930
  end
931
931
  end
932
932
 
@@ -1298,26 +1298,25 @@ module Squared
1298
1298
  file = if (val = env('LOG_FILE'))
1299
1299
  Time.now.strftime(val)
1300
1300
  elsif (val = env('LOG_AUTO'))
1301
- require 'date'
1301
+ d = Time.now
1302
1302
  "#{@name}-%s.log" % [case val
1303
1303
  when 'y', 'year'
1304
- Date.today.year
1304
+ d.year
1305
1305
  when 'm', 'month'
1306
- Date.today.strftime('%Y-%m')
1306
+ d.strftime('%Y-%m')
1307
1307
  when 'd', 'day', '1'
1308
- Date.today
1308
+ d.strftime('%Y-%m-%d')
1309
1309
  else
1310
- val.include?('%') ? Time.now.strftime(val) : Time.now.strftime('%FT%T%:z')
1310
+ val.include?('%') ? d.strftime(val) : d.strftime('%FT%T%:z')
1311
1311
  end]
1312
1312
  elsif (val = log[:file])
1313
1313
  if val.is_a?(String)
1314
1314
  Time.now.strftime(val)
1315
1315
  else
1316
- require 'date'
1317
- "#{@name}-#{Date.today}.log"
1316
+ "#{@name}-#{Time.now.strftime('%Y-%m-%d')}.log"
1318
1317
  end
1319
1318
  end
1320
- .yield_self do |path|
1319
+ .then do |path|
1321
1320
  next unless path
1322
1321
 
1323
1322
  @workspace.home.join(env('LOG_DIR', ''), path).realdirpath
@@ -1476,7 +1475,7 @@ module Squared
1476
1475
 
1477
1476
  t = uniq.call(name = proj.name)
1478
1477
  j = if out
1479
- if i == items.size.pred || (post = items[i.succ..-1] - done).empty?
1478
+ if i == items.size.pred || (post = items[i.succ..] - done).empty?
1480
1479
  true
1481
1480
  elsif !t.empty? && depth > 0
1482
1481
  (post - t).empty?
@@ -1582,14 +1581,18 @@ module Squared
1582
1581
  end
1583
1582
 
1584
1583
  def env(key, default = nil, suffix: nil, strict: false, ignore: nil, **kwargs, &blk)
1585
- name = "#{key}_#{envname_get}"
1584
+ name = suffix == false ? key : "#{key}_#{envname_get}"
1586
1585
  ret = if suffix
1587
1586
  ENV["#{name}_#{suffix}"] || (ignore == false && ENV["#{key}_#{suffix}"]) || ''
1588
1587
  elsif strict
1589
1588
  ENV[name].to_s
1590
1589
  else
1591
1590
  ignore = ['0'].freeze if ignore.nil?
1592
- ENV[name] || ENV.fetch(key, '')
1591
+ if key =~ /^(.+)_(FORCE|YES|Y)$/ && !(ret = __arg__($2 == 'Y' ? 'YES' : $2, $1)).nil?
1592
+ ret
1593
+ else
1594
+ ENV[name] || ENV.fetch(key, '')
1595
+ end
1593
1596
  end
1594
1597
  env_yield(ret, default, ignore: ignore, **kwargs, &blk)
1595
1598
  end
@@ -1693,19 +1696,38 @@ module Squared
1693
1696
  **kwargs)
1694
1697
  return unless prefix
1695
1698
 
1699
+ getarg = ->(key) { ARG[key][s = prefix.downcase] || ARG[key][s.to_sym] }
1696
1700
  args.each do |key|
1697
- next unless (ret = env(env_key(prefix.to_s.stripext, key), **kwargs)) && (!pat || ret.match?(pat))
1698
-
1699
- if target
1700
- if path
1701
- target << quote_option(key, basepath(ret))
1702
- elsif quote
1703
- target << quote_option(key, ret)
1704
- elsif escape
1705
- target << shell_option(key, ret)
1701
+ ret = case key.downcase
1702
+ when 'force'
1703
+ getarg.call(:FORCE)
1704
+ when 'y'
1705
+ getarg.call(:YES)
1706
+ end
1707
+ return yield(ret) if !ret.nil? && block_given?
1708
+
1709
+ case ret.to_s
1710
+ when '0'
1711
+ next
1712
+ when 'true'
1713
+ return true
1714
+ when 'false'
1715
+ return false
1716
+ else
1717
+ ret ||= env(env_key(prefix.to_s.stripext, key), **kwargs)
1718
+ next unless ret && (!pat || ret.match?(pat))
1719
+
1720
+ if target
1721
+ if path
1722
+ target << quote_option(key, basepath(ret))
1723
+ elsif quote
1724
+ target << quote_option(key, ret)
1725
+ elsif escape
1726
+ target << shell_option(key, ret)
1727
+ end
1706
1728
  end
1729
+ return block_given? ? yield(ret) : ret
1707
1730
  end
1708
- return block_given? ? yield(ret) : ret
1709
1731
  end
1710
1732
  nil
1711
1733
  end
@@ -1818,6 +1840,8 @@ module Squared
1818
1840
 
1819
1841
  case from
1820
1842
  when :outdated
1843
+ return if ENV['BANNER'] == 'false'
1844
+
1821
1845
  out = print_footer("major #{args[0]} / minor #{args[1]} / patch #{args[2]}", right: true).split("\n")
1822
1846
  sub_style!(out[1], **opt_style(theme[:major], /^( +major )(\d+)(.+)$/, 2))
1823
1847
  sub_style!(out[1], **opt_style(theme[:active], /^(.+)(minor )(\d+)(.+)$/, 3))
@@ -2040,27 +2064,27 @@ module Squared
2040
2064
  val
2041
2065
  end
2042
2066
  end
2043
- base.update(data)
2044
- .update(ret)
2067
+ base.update(data, ret)
2045
2068
  end
2046
2069
 
2047
2070
  def merge_opts(base, data)
2071
+ s = '%s %s'
2048
2072
  case data
2049
2073
  when String
2050
2074
  case base
2051
2075
  when String
2052
- "#{base} #{data}"
2076
+ s % [base, data]
2053
2077
  when Hash
2054
- "#{append_hash(base, target: []).join(' ')} #{data}"
2078
+ s % [append_hash(base, target: []).join(' '), data]
2055
2079
  when Enumerable
2056
- "#{base.to_a.join(' ')} #{data}"
2080
+ s % [base.to_a.join(' '), data]
2057
2081
  else
2058
2082
  data
2059
2083
  end
2060
2084
  when Hash
2061
2085
  case base
2062
2086
  when String
2063
- "#{base} #{append_hash(data, target: []).join(' ')}"
2087
+ s % [base, append_hash(data, target: []).join(' ')]
2064
2088
  when Hash
2065
2089
  base.merge(data)
2066
2090
  when Enumerable
@@ -2071,9 +2095,9 @@ module Squared
2071
2095
  when Enumerable
2072
2096
  case base
2073
2097
  when String
2074
- "#{base} #{data.to_a.join(' ')}"
2098
+ s % [base, data.to_a.join(' ')]
2075
2099
  when Hash
2076
- "#{append_hash(base, target: []).join(' ')} #{data.to_a.join(' ')}"
2100
+ s % [append_hash(base, target: []).join(' '), data.to_a.join(' ')]
2077
2101
  when Enumerable
2078
2102
  Set.new(base.to_a + data.to_a).to_a
2079
2103
  else
@@ -2107,8 +2131,23 @@ module Squared
2107
2131
  end || []
2108
2132
  end
2109
2133
 
2134
+ def parse_select(kwargs = {})
2135
+ pa, ch = ENV.fetch('SELECT', '0:0').split(':').map(&:to_i)
2136
+ if pa > 0
2137
+ kwargs[:page] = pa
2138
+ elsif !kwargs.key?(:page)
2139
+ kwargs[:page] = ARG[:PAGE]
2140
+ end
2141
+ kwargs[:max] = ch if ch > 0
2142
+ kwargs
2143
+ end
2144
+
2110
2145
  def param_guard(action, flag, args:, key: nil, pat: nil, values: nil)
2111
2146
  if args && key
2147
+ if key.is_a?(Array)
2148
+ return key.map { |val| param_guard(action, flag, args: args, key: val, pat: pat, values: values) }
2149
+ end
2150
+
2112
2151
  val = args.fetch(key, nil)
2113
2152
  return val unless val.nil? || (pat && !val.match?(pat)) || (values && !values.include?(val))
2114
2153
 
@@ -2140,9 +2179,9 @@ module Squared
2140
2179
  end
2141
2180
 
2142
2181
  def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil, multiple: false,
2143
- force: true, **kwargs)
2182
+ force: true, pipe: self.pipe, **kwargs)
2144
2183
  puts unless series || printfirst?
2145
- ret = choice(msg, list, multiple: multiple, force: force, **kwargs)
2184
+ ret = choice(msg, list, multiple: multiple, force: force, pipe: pipe, **parse_select(kwargs))
2146
2185
  unless ret && !ret.empty?
2147
2186
  exit 1 if force
2148
2187
  return
@@ -2226,12 +2265,12 @@ module Squared
2226
2265
  end
2227
2266
 
2228
2267
  def relativepath(*list, all: false)
2229
- list.map { |val| Pathname.new(val) }
2230
- .select { |val| projectpath?(val) }
2231
- .map do |val|
2232
- ret = (val.absolute? ? val.relative_path_from(path) : val.cleanpath).to_s
2233
- all && val.to_s.end_with?('/') ? "#{ret}/*" : ret
2234
- end
2268
+ list.filter_map do |val|
2269
+ next unless projectpath?(file = Pathname.new(val))
2270
+
2271
+ out = (file.absolute? ? file.relative_path_from(path) : file.cleanpath).to_s
2272
+ all && file.to_s.end_with?('/') ? "#{out}/*" : out
2273
+ end
2235
2274
  end
2236
2275
 
2237
2276
  def projectmap(files, resolve: true, parent: false, pass: true)
@@ -2334,6 +2373,19 @@ module Squared
2334
2373
  cur[2] == lat[2] ? 3 : 2
2335
2374
  end
2336
2375
 
2376
+ def semapp(name, *args, minor: true, patch: true, pre: false, env: false)
2377
+ if args.empty?
2378
+ args << '--version'
2379
+ else
2380
+ args.map! { |s| shell_quote(s, force: false) }
2381
+ end
2382
+ n = minor ? patch ? pre ? -1 : 4 : 2 : 0
2383
+ IO.popen([shell_bin(name, env: env), *args], chdir: path, &:read).scan(SEM_VER).first
2384
+ &.slice(0..n)&.join or raise
2385
+ rescue
2386
+ '0.0.0'
2387
+ end
2388
+
2337
2389
  def semmajor?(cur, want)
2338
2390
  (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[6]
2339
2391
  end
@@ -2347,7 +2399,7 @@ module Squared
2347
2399
  end
2348
2400
 
2349
2401
  def indexitem(val)
2350
- [$1.to_i, $2 && $2[1..-1]] if val =~ /\A[=^#{indexchar}](\d+)(:.+)?\z/
2402
+ [$1.to_i, $2 && $2[1..]] if val =~ /\A[=^#{indexchar}](\d+)(:.+)?\z/
2351
2403
  end
2352
2404
 
2353
2405
  def indexerror(val, list = nil)
@@ -2451,7 +2503,6 @@ module Squared
2451
2503
  def pwd_set(cmd = nil, pass: false, timeout: nil, from: @on[:from].last, **kwargs, &blk)
2452
2504
  return if from == false
2453
2505
 
2454
- require 'timeout'
2455
2506
  unless (path.to_s == Dir.pwd || pass) && (mri? || !windows?)
2456
2507
  pwd = Dir.pwd
2457
2508
  Dir.chdir(path)
@@ -2748,7 +2799,15 @@ module Squared
2748
2799
  end
2749
2800
 
2750
2801
  def dryrun?(target: @session, prefix: target&.first, **)
2751
- Array(target).include?('--dry-run') || !option('dry-run', target: target, prefix: prefix).nil?
2802
+ val = ENV['DRY_RUN'] if (val = option('dry-run', target: target, prefix: prefix)).nil?
2803
+ case val.to_s
2804
+ when '0' then nil
2805
+ when 'false'
2806
+ return false
2807
+ else
2808
+ return true if val
2809
+ end
2810
+ Array(target).include?('--dry-run')
2752
2811
  end
2753
2812
 
2754
2813
  def pwd?
@@ -128,6 +128,7 @@ module Squared
128
128
  'image' => %i[ls rm pull push tag save inspect history].freeze,
129
129
  'container' => %i[attach commit create diff exec inspect kill logs pause restart rm run start stats stop top
130
130
  unpause update].freeze,
131
+ 'run' => %i[rmi rmd mount volume entrypoint].freeze,
131
132
  'network' => %i[connect disconnect create rm].freeze,
132
133
  'ls' => nil
133
134
  })
@@ -303,13 +304,22 @@ module Squared
303
304
  end
304
305
  end
305
306
  else
306
- format_desc action, flag, "opts*,id/name#{case flag
307
- when :stats then '*'
308
- when :logs, :diff, :attach, :top then nil
309
- else '+'
310
- end}"
307
+ single = %i[logs diff attach top].freeze
308
+ format_desc action, flag, "opts*,id/name#{if flag == :stats
309
+ '*'
310
+ elsif !single.include?(flag)
311
+ '+'
312
+ end}|:"
311
313
  task flag do |_, args|
312
- container flag, args.to_a
314
+ args = args.to_a
315
+ if choice!(args, first: false)
316
+ args.concat(Array(choice_command(:container, multiple: !single.include?(flag))))
317
+ if flag == :rm
318
+ stop = filter_ps(:stop, column: 'ID').select { |id| args.include?(id) }
319
+ container :stop, stop if !stop.empty? && confirm_basic('Stop?', stop.join(', '))
320
+ end
321
+ end
322
+ container flag, args
313
323
  end
314
324
  end
315
325
  when 'image'
@@ -358,18 +368,47 @@ module Squared
358
368
  end
359
369
  end
360
370
  end
371
+ when 'run'
372
+ format_desc action, flag, 'id|:,opts*'
373
+ task flag, [:id] do |_, args|
374
+ id = if args.id == ':'
375
+ choice_command(:image, multiple: false)
376
+ else
377
+ param_guard(action, flag, args: args, key: :id)
378
+ end
379
+ args = case flag
380
+ when :mount
381
+ mount = []
382
+ loop do
383
+ s = readline("Enter mount ##{mount.size.succ}", force: false)
384
+ break if s.empty?
385
+
386
+ mount << (s.include?('type=') ? s : "type=bind,#{s}")
387
+ end
388
+ mount.map { |val| quote_option('mount', val, double: true) }
389
+ when :volume
390
+ choice_command(:volume, multiple: true, series: true).map do |val|
391
+ basic_option 'volume', val
392
+ end
393
+ when :entrypoint
394
+ ["entrypoint=#{shell_quote(readline('Entrypoint', force: true), double: true)}"]
395
+ when :rmi
396
+ %w[rm i t]
397
+ when :rmd
398
+ %w[rm d]
399
+ end + args.extras
400
+ container(:run, args.uniq, id: id)
401
+ end
361
402
  when 'network'
362
403
  format_desc action, flag, 'name,opts*'
363
404
  task flag, [:name] do |_, args|
364
- name = if flag == :create || flag == :rm
365
- param_guard(action, flag, args: args, key: :name)
366
- else
367
- args.name
368
- end
405
+ name = flag == :create ? param_guard(action, flag, args: args, key: :name) : args.name
369
406
  if name
370
407
  network(flag, args.extras, name: name)
408
+ elsif flag == :rm
409
+ choice_command :'network:rm'
371
410
  else
372
- choice_command flag
411
+ choice_command(flag, id: choice_command(:container, force: false), series: true)
373
412
  end
374
413
  end
375
414
  end
@@ -501,7 +540,7 @@ module Squared
501
540
  if flag == :publish
502
541
  id ||= option('tag', ignore: false) || op.shift || tagmain
503
542
  registry ||= option('registry') || op.shift || @oci
504
- emptyargs op, flag
543
+ op.clear(strict: true)
505
544
  op << shell_quote(tagjoin(registry, id))
506
545
  return unless confirm_command(op.to_s, title: from, target: id)
507
546
  else
@@ -516,10 +555,10 @@ module Squared
516
555
  if multiple
517
556
  op.concat(service) if service
518
557
  op.append(delim: true, escape: true, strip: ':')
519
- else
520
- raise ArgumentError, message('no service was selected', hint: flag) unless service
521
-
558
+ elsif service
522
559
  append_command(flag, service, op.extras, prompt: '::')
560
+ else
561
+ raise ArgumentError, message('no service was selected', hint: flag)
523
562
  end
524
563
  end
525
564
  end
@@ -668,7 +707,7 @@ module Squared
668
707
  args = if name
669
708
  index += 1
670
709
  opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
671
- elsif choice!(opts, only: true)
710
+ elsif choice!(opts, empty: true, only: true)
672
711
  OptionPartition.strip(readline('Enter arguments', force: false))
673
712
  else
674
713
  opts
@@ -720,9 +759,9 @@ module Squared
720
759
  when :push
721
760
  id ||= option('tag', ignore: false) || op.shift || tagmain
722
761
  registry ||= option('registry') || op.shift || self.registry
723
- emptyargs op, flag
724
762
  raise ArgumentError, message('username/registry not specified', hint: flag) unless registry
725
763
 
764
+ op.clear(strict: true)
726
765
  uri = shell_quote tagjoin(registry, id)
727
766
  op << uri
728
767
  img = docker_output 'image tag', id, uri
@@ -745,7 +784,7 @@ module Squared
745
784
  success?(run(sync: sync, exception: exception, banner: banner, from: from), status)
746
785
  end
747
786
 
748
- def network(flag, opts = [], target: nil, name: target)
787
+ def network(flag, opts = [], name: nil)
749
788
  cmd, opts = docker_session('network', flag, opts: opts)
750
789
  op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, **dockerinit)
751
790
  .clear
@@ -891,7 +930,7 @@ module Squared
891
930
  target
892
931
  end
893
932
 
894
- def filter_ps(flag, meth = nil, column: nil, from: meth || :'container:ps')
933
+ def filter_ps(flag, column: nil, from: :'container:ps')
895
934
  no = false
896
935
  status = case flag.to_sym
897
936
  when :start
@@ -927,7 +966,7 @@ module Squared
927
966
  pwd_set(from: from) do
928
967
  index = 1
929
968
  all = option('all', prefix: 'docker')
930
- y = from.to_s.end_with?(':rm') && option('y', prefix: 'docker')
969
+ y = from == :'image:rm' && option('y', prefix: 'docker')
931
970
  filter = env('DOCKER_FILTER', filter).to_s
932
971
  pat = if OptionPartition.pattern?(filter)
933
972
  Regexp.new(filter)
@@ -1009,18 +1048,22 @@ module Squared
1009
1048
  confirm "#{s} #{sub_style(target, theme[:subject])}#{" as #{sub_style(as, theme[:inline])}" if as}?", 'N'
1010
1049
  end
1011
1050
 
1012
- def choice_command(flag, *action)
1051
+ def choice_command(flag, *action, id: nil, multiple: nil, series: false, force: true)
1013
1052
  msg, cmd = case flag
1014
- when :exec
1053
+ when :exec, :container
1015
1054
  ['Choose a container', 'ps -a']
1016
1055
  when :bake
1017
1056
  ['Choose a target', 'buildx bake --list=type=targets']
1018
- when :connect, :disconnect
1057
+ when :connect, :disconnect, :'network:rm'
1019
1058
  ['Choose a network', 'network ls']
1020
1059
  when :service
1021
1060
  ['Choose a service',
1022
1061
  'compose ps -a ' \
1023
1062
  '--format="table {{.Service}}\t{{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Status}}\t{{.Ports}}"']
1063
+ when :volume
1064
+ ['Choose a volume',
1065
+ 'volume ls ' \
1066
+ '--format="table {{.Name}}\t{{.Driver}}\t{{.Mountpoint}}"']
1024
1067
  else
1025
1068
  ['Choose an image',
1026
1069
  'images -a ' \
@@ -1031,28 +1074,40 @@ module Squared
1031
1074
  puts log_message('none found', subject: name, hint: "docker #{cmd.split(' ', 3)[0, 2].join(' ')}")
1032
1075
  return
1033
1076
  end
1034
- puts " # #{lines.shift}"
1035
- multiple = false
1077
+ parse = lambda do |list|
1078
+ ret = Array(list).map { |val| val.split(/\s+/, 2).first }.reject(&:empty?)
1079
+ return ret.first if multiple == false
1080
+
1081
+ ret unless ret.empty?
1082
+ end
1083
+ puts "#{"\n" if id && !printfirst?} # #{lines.shift}"
1036
1084
  ctx = flag.to_s
1037
1085
  case flag
1038
1086
  when :run, :exec
1039
1087
  values = [['Options', flag == :run], ['Arguments', flag == :exec]]
1040
1088
  when :rm, :bake
1041
1089
  values = ['Options']
1042
- multiple = true
1090
+ multiple = true if multiple.nil?
1043
1091
  ctx = flag == :rm ? 'image rm' : "buildx bake -f #{shell_quote(dockerfile)}"
1044
1092
  when :save
1045
1093
  values = [['Output', true], 'Platform']
1046
- multiple = true
1094
+ multiple = true if multiple.nil?
1047
1095
  when :service
1048
- values = []
1049
- multiple = true
1096
+ values = ['Options']
1097
+ multiple = true if multiple.nil?
1050
1098
  ctx = 'compose'
1051
1099
  when :connect, :disconnect
1052
- values = ['Options', ['Container', true]]
1100
+ values = id ? ['Options'] : ['Options', ['Container', true]]
1053
1101
  ctx = "network #{flag}"
1102
+ when :'network:rm'
1103
+ values = ['Options']
1104
+ multiple = true if multiple.nil?
1105
+ ctx = 'network rm'
1106
+ when :container, :image, :volume
1107
+ return parse.call(choice_index(msg, lines, multiple: multiple, series: series, force: force,
1108
+ attempts: force ? 3 : 1))
1054
1109
  end
1055
- out, opts, args = choice_index(msg, lines, multiple: multiple, values: values)
1110
+ out, opts, args = choice_index(msg, lines, values: values, multiple: multiple, series: series)
1056
1111
  cmd = docker_output(ctx, *action)
1057
1112
  case flag
1058
1113
  when :tag
@@ -1067,8 +1122,10 @@ module Squared
1067
1122
  else
1068
1123
  cmd << opts << '--'
1069
1124
  end
1070
- cmd.merge(Array(out).map { |val| val.split(/\s+/, 2).first })
1071
- success?(run(cmd << args, { 'NO_COLOR' => 'true' }), ctx.start_with?('network', 'tag', 'save'))
1125
+ cmd.merge(parse.call(out))
1126
+ cmd << args
1127
+ cmd.merge(Array(id)) if id
1128
+ success?(run(cmd, { 'NO_COLOR' => 'true' }), ctx.start_with?('network', 'tag', 'save'))
1072
1129
  end
1073
1130
 
1074
1131
  def filetype(val = dockerfile)
@@ -1108,14 +1165,14 @@ module Squared
1108
1165
  s = val.join(':')
1109
1166
  return s unless s.size > max
1110
1167
  end
1111
- ret[0...max]
1168
+ ret[...max]
1112
1169
  end
1113
1170
 
1114
1171
  def dnsname(val, max: 63, char: nil)
1115
1172
  ret = val.sub(/^[^a-z]+/i, '')
1116
1173
  .sub(/[^a-z\d]+$/i, '')
1117
1174
  .gsub(/[^a-z\d#{char}]+/i, '-')
1118
- .gsub(/-{2,}/, '-')[0...max]
1175
+ .gsub(/-{2,}/, '-')[...max]
1119
1176
  char ? ret : ret.downcase
1120
1177
  end
1121
1178
 
@@ -1123,10 +1180,6 @@ module Squared
1123
1180
  dnsname(val, max: 128, char: '.').gsub(/\.{2,}/, '.')
1124
1181
  end
1125
1182
 
1126
- def charname(val)
1127
- val.gsub(/[^\w.-]+/, '_')
1128
- end
1129
-
1130
1183
  def tagmain
1131
1184
  tag.is_a?(Array) ? tag.first : tag
1132
1185
  end
@@ -1135,10 +1188,6 @@ module Squared
1135
1188
  { project: self, strict: strict?, explicit: '=', bool: %w[1 0 true false] }
1136
1189
  end
1137
1190
 
1138
- def emptyargs(list, hint = nil)
1139
- raise ArgumentError, message('unrecognized args', list.join(', '), hint: hint) unless list.empty?
1140
- end
1141
-
1142
1191
  def anypath?(*args)
1143
1192
  args.any? { |val| basepath!(val) }
1144
1193
  end