squared 0.4.10 → 0.4.12

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.
@@ -212,16 +212,11 @@ module Squared
212
212
  elsif (val = log[:file])
213
213
  file = val.is_a?(String) ? Time.now.strftime(val) : "#{@name}-#{Date.today}.log"
214
214
  end
215
- if file
216
- file = (val = env('LOG_DIR')) ? @workspace.home.join(val, file) : @workspace.home + file
217
- begin
218
- file = file.realdirpath
219
- rescue StandardError => e
220
- raise if @exception
221
-
222
- file = nil
223
- warn log_message(Logger::WARN, e, pass: true) if warning?
224
- end
215
+ begin
216
+ file &&= @workspace.home.join(env('LOG_DIR', ''), file).realdirpath
217
+ rescue StandardError => e
218
+ file = nil
219
+ warn log_message(Logger::WARN, e, pass: true) if warning?
225
220
  end
226
221
  log[:progname] ||= @name
227
222
  if (val = env('LOG_LEVEL', ignore: false))
@@ -355,7 +350,7 @@ module Squared
355
350
  false
356
351
  end
357
352
  end
358
- if path.is_a?(String) && (seg = path[%r{^(.+)[\\/]\*+$}, 1])
353
+ if path.is_a?(String) && (seg = path[%r{\A(.+)[\\/]\*+\z}, 1])
359
354
  return self unless checkdir.call(path = basepath(seg))
360
355
 
361
356
  path = path.children.select { |val| checkdir.call(val) }
@@ -366,10 +361,11 @@ module Squared
366
361
  return self
367
362
  elsif !projectpath?(path = basepath(path)) || !checkdir.call(path)
368
363
  return self
369
- elsif name.is_a?(Symbol)
370
- name = name.to_s
371
- elsif !name.is_a?(String)
372
- name = nil
364
+ else
365
+ name = case name
366
+ when String, Symbol
367
+ name.to_s
368
+ end
373
369
  end
374
370
  if @withargs
375
371
  data = @withargs.dup
@@ -389,6 +385,11 @@ module Squared
389
385
  self
390
386
  end
391
387
 
388
+ def chain(*args, **kwargs)
389
+ workspace.chain(*args, project: self, **kwargs)
390
+ self
391
+ end
392
+
392
393
  def inject(obj, *args, **kwargs, &blk)
393
394
  return self unless enabled?
394
395
 
@@ -518,7 +519,7 @@ module Squared
518
519
  if @clean.is_a?(Enumerable) && !series?(@clean)
519
520
  @clean.each do |val|
520
521
  entry = path + (val = val.to_s)
521
- if entry.directory? && val.match?(%r{[\\/]$})
522
+ if entry.directory? && val.match?(%r{[\\/]\z})
522
523
  log&.warn "rm -rf #{entry}"
523
524
  rm_rf(entry, verbose: verbose)
524
525
  else
@@ -572,8 +573,7 @@ module Squared
572
573
  end
573
574
  end
574
575
 
575
- def unpack(target, uri:, sync: true, digest: nil, ext: nil, force: false, depth: 1, headers: {},
576
- from: :unpack)
576
+ def unpack(target, uri:, sync: true, digest: nil, ext: nil, force: false, depth: 1, headers: {}, from: :unpack)
577
577
  if !target.exist?
578
578
  target.mkpath
579
579
  elsif !target.directory?
@@ -581,18 +581,11 @@ module Squared
581
581
  elsif !target.empty?
582
582
  raise_error('directory not empty', hint: target) unless force || env('UNPACK_FORCE')
583
583
  create = true
584
- elsif !uri
585
- raise_error('no download uri', hint: target)
586
584
  end
587
585
  if digest
588
586
  require 'digest'
589
- if (n = digest.index(':').to_i) > 0
590
- size = digest[0, n].downcase
591
- digest = digest[n + 1..-1]
592
- else
593
- size = digest.size
594
- end
595
- algo = case size
587
+ digest, type = digest.split(':', 2).reverse
588
+ algo = case type&.downcase || digest.size
596
589
  when 32, 'md5'
597
590
  Digest::MD5
598
591
  when 'rmd160'
@@ -610,16 +603,15 @@ module Squared
610
603
  end
611
604
  end
612
605
  if (val = env('HEADERS')) && (val = parse_json(val, hint: "HEADERS_#{@envname}"))
613
- headers = val
606
+ headers = headers.is_a?(Hash) ? headers.merge(val) : val
614
607
  end
615
608
  data = nil
616
609
  (uri = as_a(uri)).each_with_index do |url, index|
617
- last = index == uri.size - 1
618
610
  fetch_uri(url, headers) do |f|
619
611
  data = f.read
620
612
  if algo && algo.hexdigest(data) != digest
621
613
  data = nil
622
- raise_error("checksum failed: #{digest}", hint: url) if last
614
+ raise_error("checksum failed: #{digest}", hint: url) if index == uri.size - 1
623
615
  end
624
616
  next if ext && index == 0
625
617
 
@@ -632,14 +624,11 @@ module Squared
632
624
  ext = 'txz'
633
625
  end
634
626
  end
635
- if data
636
- uri = url
637
- break
638
- elsif last
639
- raise_error('no content', hint: url)
640
- end
627
+ break uri = url if data
628
+ end
629
+ unless data && (ext ||= URI.parse(uri).path[/\.(\w+)(?:\?|\z)/, 1])
630
+ raise_error("no content#{data ? ' type' : ''}", hint: uri)
641
631
  end
642
- raise_error('no content type', hint: uri) unless ext ||= URI.parse(uri).path[/\.(\w+)(\?|$)/i, 1]
643
632
  ext = ext.downcase
644
633
  if (val = env("#{%w[zip 7z gem].include?(ext) ? ext.upcase : 'TAR'}_DEPTH", ignore: false))
645
634
  depth = val.to_i
@@ -698,8 +687,8 @@ module Squared
698
687
  ensure
699
688
  if dir
700
689
  remove_entry dir
701
- elsif file
702
- file.unlink
690
+ else
691
+ file&.unlink
703
692
  end
704
693
  end
705
694
  end
@@ -916,6 +905,7 @@ module Squared
916
905
  end
917
906
  return
918
907
  end
908
+ cmd = cmd.target if cmd.is_a?(OptionPartition)
919
909
  cmd = session_done cmd
920
910
  log&.info cmd
921
911
  on :first, from
@@ -1178,8 +1168,8 @@ module Squared
1178
1168
  end
1179
1169
 
1180
1170
  def print_item(*val)
1181
- puts if @@print_order > 0 && stdout?
1182
- @@print_order += 1
1171
+ puts if !printfirst? && stdout?
1172
+ printsucc
1183
1173
  puts val unless val.empty? || (val.size == 1 && val.first.nil?)
1184
1174
  end
1185
1175
 
@@ -1209,10 +1199,9 @@ module Squared
1209
1199
 
1210
1200
  def print_footer(*lines, sub: nil, reverse: false, right: false, **kwargs)
1211
1201
  n = Project.max_width(lines)
1212
- sub = as_a sub
1213
1202
  lines.map! do |val|
1214
1203
  s = right ? val.rjust(n) : val.ljust(n)
1215
- sub.each { |h| s = sub_style(s, **h) }
1204
+ sub&.each { |h| s = sub_style(s, **h) }
1216
1205
  s
1217
1206
  end
1218
1207
  ret = [sub_style(ARG[:BORDER][1] * n, styles: kwargs.key?(:border) ? kwargs[:border] : borderstyle), *lines]
@@ -1243,7 +1232,7 @@ module Squared
1243
1232
  if data[:command]
1244
1233
  if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+)) /
1245
1234
  path = $3 || $2 || $1
1246
- cmd = cmd.sub(path, File.basename(path).upcase)
1235
+ cmd = cmd.sub(path, stripext(path).upcase)
1247
1236
  end
1248
1237
  out << cmd
1249
1238
  end
@@ -1502,12 +1491,12 @@ module Squared
1502
1491
  b = sub_style("#{pkg} #{ver}", styles: theme[:inline])
1503
1492
  c, d = rev == 1 || lock ? ['y/N', 'N'] : ['Y/n', 'Y']
1504
1493
  e = lock ? " #{sub_style('(locked)', styles: color(:red))}" : ''
1505
- confirm("Upgrade to #{a}? #{b + e} [#{c}] ", d)
1494
+ confirm "Upgrade to #{a}? #{b + e} [#{c}] ", d
1506
1495
  end
1507
1496
 
1508
1497
  def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil,
1509
1498
  multiple: false, force: true, **kwargs)
1510
- puts if !series && @@print_order > 0
1499
+ puts if !series && !printfirst?
1511
1500
  msg = "#{msg} (optional)" unless force
1512
1501
  unless (ret = choice(msg, list, multiple: multiple, force: force, **kwargs)) || !force
1513
1502
  raise_error 'user cancelled'
@@ -1519,7 +1508,7 @@ module Squared
1519
1508
  end
1520
1509
  ret = multiple ? ret.map! { |val| val.sub(trim, '') } : ret.sub(trim, '') if trim
1521
1510
  if column
1522
- a, b = as_a(column)
1511
+ a, b = as_a column
1523
1512
  ret = as_a(ret).map! { |val| val[a, b || 1] }
1524
1513
  ret = ret.first unless multiple
1525
1514
  end
@@ -1554,7 +1543,7 @@ module Squared
1554
1543
  ret << (val.empty? ? nil : val)
1555
1544
  end
1556
1545
  end
1557
- @@print_order += 1 unless series
1546
+ printsucc unless series
1558
1547
  ret
1559
1548
  end
1560
1549
 
@@ -1608,15 +1597,17 @@ module Squared
1608
1597
  end
1609
1598
 
1610
1599
  def indexitem(val)
1611
- return unless val =~ /\A\^(\d+)(:.+)?\z/
1612
-
1613
- [$1.to_i, $2 && $2[1..-1]]
1600
+ [$1.to_i, $2 && $2[1..-1]] if val =~ /\A\^(\d+)(:.+)?\z/
1614
1601
  end
1615
1602
 
1616
1603
  def indexerror(val, list = nil)
1617
1604
  raise_error("requested index #{val}", hint: list && "of #{list.size}")
1618
1605
  end
1619
1606
 
1607
+ def printsucc
1608
+ @@print_order += 1
1609
+ end
1610
+
1620
1611
  def color(val)
1621
1612
  ret = theme[val]
1622
1613
  ret && !ret.empty? ? ret : [val]
@@ -1642,16 +1633,14 @@ module Squared
1642
1633
  end
1643
1634
 
1644
1635
  def on(event, from, *args, **kwargs)
1645
- return unless from
1646
-
1647
- @events[event][from]&.each do |obj|
1648
- if obj.is_a?(Array) && obj[1].is_a?(Hash)
1649
- opts = kwargs.empty? ? obj[1] : obj[1].merge(kwargs)
1650
- target = obj[0]
1651
- else
1652
- opts = kwargs
1653
- target = obj
1654
- end
1636
+ return unless from && (data = @events[event])
1637
+
1638
+ data[from]&.each do |obj|
1639
+ target, opts = if obj.is_a?(Array) && obj[1].is_a?(Hash)
1640
+ [obj[0], kwargs.empty? ? obj[1] : obj[1].merge(kwargs)]
1641
+ else
1642
+ [obj, kwargs]
1643
+ end
1655
1644
  as_a(target, flat: true).each do |cmd|
1656
1645
  case cmd
1657
1646
  when Proc, Method
@@ -1796,6 +1785,10 @@ module Squared
1796
1785
  (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
1797
1786
  end
1798
1787
 
1788
+ def printfirst?
1789
+ @@print_order == 0
1790
+ end
1791
+
1799
1792
  def runnable?(val)
1800
1793
  case val
1801
1794
  when String, Enumerable, Proc, Method
@@ -1825,8 +1818,9 @@ module Squared
1825
1818
  return true if val || from_sync?(ac = workspace.task_name(action))
1826
1819
  return val if group && !(val = from_sync?(ac, group)).nil?
1827
1820
  return val if (base = workspace.find_base(self)) && !(val = from_sync?(ac, base.ref)).nil?
1821
+ return false if workspace.series.chain?(val = task_join(name, action))
1828
1822
 
1829
- if task_invoked?(task_join(name, ac)) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
1823
+ if task_invoked?(val) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
1830
1824
  true
1831
1825
  else
1832
1826
  val = workspace.series.name_get(action)
@@ -138,10 +138,14 @@ module Squared
138
138
  buildx(:build, args.extras, "#{flag}": param)
139
139
  end
140
140
  when :bake
141
- format_desc action, flag, 'opts*,target*,context?'
141
+ format_desc action, flag, ':?,opts*,target*,context?'
142
142
  task flag do |_, args|
143
- args = param_guard(action, flag, args: args.to_a)
144
- buildx flag, args
143
+ args = args.to_a
144
+ if args.first == ':'
145
+ choice_command :bake
146
+ else
147
+ buildx flag, args
148
+ end
145
149
  end
146
150
  end
147
151
  when 'compose'
@@ -226,11 +230,11 @@ module Squared
226
230
  image(:rm, sync: sync)
227
231
  end
228
232
 
229
- def compose(opts, flags = nil, script: false, args: nil, from: :build, **)
233
+ def compose(opts, flags = nil, script: false, args: nil, from: :run, **)
230
234
  return opts if script == false
231
235
 
232
236
  ret = docker_session
233
- if from == :build
237
+ if from == :run
234
238
  case (n = filetype)
235
239
  when 1, 2
236
240
  ret << 'buildx' << 'bake'
@@ -254,14 +258,16 @@ module Squared
254
258
  when Enumerable
255
259
  ret.merge(opts.to_a)
256
260
  end
257
- [args, flags].each_with_index do |target, index|
258
- next unless target
259
261
 
260
- target = append_any(target, target: []) unless target.is_a?(Array)
261
- ret.merge(target.map { |arg| index == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
262
+ [args, flags].each_with_index do |target, index|
263
+ if target.is_a?(String)
264
+ ret << target
265
+ elsif (target = append_any(target, target: []))
266
+ ret.merge(target.map { |arg| index == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
267
+ end
262
268
  end
263
269
  case from
264
- when :build
270
+ when :run
265
271
  case @secrets
266
272
  when String
267
273
  ret << quote_option('secret', @secrets, double: true)
@@ -647,11 +653,11 @@ module Squared
647
653
  index += 1
648
654
  next unless confirm("#{h + b}? [#{c}] ", d, timeout: 60)
649
655
 
650
- puts if @@print_order == 0
656
+ puts if printfirst?
651
657
  end
652
658
  yield id
653
659
  end
654
- puts log_message(Logger::INFO, 'none detected', subject: "#{name}:#{from}", hint: hint) unless found || y
660
+ puts log_message(Logger::INFO, 'none detected', subject: "#{name}:#{from}", hint: hint) if found || y
655
661
  end
656
662
  rescue StandardError => e
657
663
  log.error e
@@ -664,13 +670,13 @@ module Squared
664
670
  def confirm_command(*args, title: nil, target: nil, as: nil)
665
671
  return false unless title && target
666
672
 
667
- puts unless @@print_order == 0
673
+ puts unless printfirst?
668
674
  t = title.to_s.split(':')
669
675
  emphasize(args, title: message(t.first.upcase, *t.drop(1)), border: borderstyle, sub: [
670
676
  { pat: /\A(\w+(?: => \w+)+)(.*)\z/, styles: theme[:header] },
671
677
  { pat: /\A(.+)\z/, styles: theme[:caution] }
672
678
  ])
673
- @@print_order += 1
679
+ printsucc
674
680
  a = t.last.capitalize
675
681
  b = sub_style(target, styles: theme[:subject])
676
682
  c = as && sub_style(as, styles: theme[:inline])
@@ -683,6 +689,8 @@ module Squared
683
689
  ['Choose an image', 'images -a', 2]
684
690
  when :exec
685
691
  ['Choose a container', 'ps -a', 0]
692
+ when :bake
693
+ ['Choose a target', 'buildx bake --list=type=targets', 0]
686
694
  else
687
695
  ['Choose a network', 'network ls', 0]
688
696
  end
@@ -698,10 +706,10 @@ module Squared
698
706
  when :run, :exec
699
707
  values = [['Options', flag == :run], ['Arguments', flag == :exec]]
700
708
  cmd = flag.to_s
701
- when :rm
709
+ when :rm, :bake
702
710
  values = ['Options']
703
711
  multiple = true
704
- cmd = "image #{flag}"
712
+ cmd = flag == :rm ? 'image rm' : "buildx bake -f #{shell_quote(dockerfile)}"
705
713
  else
706
714
  values = ['Options', ['Container', true]]
707
715
  cmd = "network #{flag}"
@@ -14,7 +14,11 @@ module Squared
14
14
  check = ->(proj) { proj.is_a?(Project::Git) && !proj.exclude?(Project::Git.ref) && git_clone?(proj.path) }
15
15
  if uri.is_a?(Array)
16
16
  base = name
17
- uri.each { |val| repo << proj if (proj = @project[val.to_s]) && check.call(proj) }
17
+ uri.each do |val|
18
+ if (proj = @project[val.to_s]) && check.call(proj)
19
+ repo << proj
20
+ end
21
+ end
18
22
  elsif uri
19
23
  data[name.to_s] = uri
20
24
  elsif name.is_a?(Enumerable)
@@ -121,20 +125,18 @@ module Squared
121
125
  def rev_write(name = nil, data = nil, sync: true, utc: nil)
122
126
  return unless @revfile
123
127
 
128
+ sleep 0 while !sync && @revlock
129
+ @revlock = true
124
130
  if name
125
131
  data&.each { |key, val| rev_entry(name, key, val: val) }
126
132
  rev_timeutc(name, utc) if utc
127
133
  end
128
- sleep 0 while !sync && @revlock
129
- begin
130
- @revlock = true
131
- File.write(@revfile, JSON.pretty_generate(@revdoc))
132
- rescue StandardError => e
133
- log&.debug e
134
- warn log_message(Logger::WARN, e, pass: true) if warning?
135
- ensure
136
- @revlock = false
137
- end
134
+ File.write(@revfile, JSON.pretty_generate(@revdoc))
135
+ rescue StandardError => e
136
+ log&.debug e
137
+ warn log_message(Logger::WARN, e, pass: true) if warning?
138
+ ensure
139
+ @revlock = false
138
140
  end
139
141
 
140
142
  def git_clone?(path, name = nil)
@@ -288,17 +290,20 @@ module Squared
288
290
  namespace(name = ws.task_name('git')) do
289
291
  all = ws.task_join(name, 'all')
290
292
 
291
- ws.format_desc(all, %w[stash|rebase depend])
293
+ ws.format_desc(all, 'stash|rebase|autostash?,depend?')
292
294
  task 'all' do |_, args|
293
- opts = args.to_a
294
- cmd = if opts.include?('stash')
295
- [ws.task_sync('stash'), ws.task_sync('pull')]
296
- elsif opts.include?('rebase')
297
- [ws.task_sync('rebase')]
295
+ args = args.to_a
296
+ cmd = if args.include?('stash')
297
+ ['stash', 'pull']
298
+ elsif args.include?('rebase')
299
+ ['rebase']
300
+ elsif args.include?('autostash')
301
+ ['autostash']
298
302
  else
299
- [ws.task_sync('pull')]
303
+ ['pull']
300
304
  end
301
- cmd << ws.task_sync('depend') if opts.include?('depend') && !ws.series[:depend].empty?
305
+ cmd.map! { |val| ws.task_sync(val) }
306
+ cmd << ws.task_sync('depend') if args.include?('depend') && !ws.series.exclude?(:depend, true)
302
307
  cmd << ws.task_sync('build')
303
308
  Common::Utils.task_invoke(*cmd, **ws.invokeargs)
304
309
  end
@@ -454,8 +459,8 @@ module Squared
454
459
  when 'stash'
455
460
  format_desc(action, flag, 'opts*', after: case flag
456
461
  when :push then 'pathspec*'
457
- when :list then nil
458
- else 'commit?' end)
462
+ when :clear, :list then nil
463
+ else 'stash?|:' end)
459
464
  task flag do |_, args|
460
465
  stash flag, args.to_a
461
466
  end
@@ -513,15 +518,7 @@ module Squared
513
518
  index = choice_commit(multiple: true)
514
519
  else
515
520
  index = []
516
- args.each do |val|
517
- if matchhead(val)
518
- index << commithead(val)
519
- elsif (sha = commithash(val))
520
- index << sha
521
- else
522
- break
523
- end
524
- end
521
+ args.each { |val| index << (commithead(val) || commithash(val) || break) }
525
522
  args = args.drop(index.size)
526
523
  end
527
524
  diff(flag, args, index: index)
@@ -1009,10 +1006,13 @@ module Squared
1009
1006
  when :push
1010
1007
  append_pathspec op.extras
1011
1008
  when :pop, :apply, :drop
1012
- unless op.empty?
1009
+ if op.extras.delete(':')
1010
+ op << choice_index('Choose a stash', git_spawn('stash list', stdout: false),
1011
+ column: /^[^@]+@\{(\d+)\}/, force: true)
1012
+ elsif !op.empty?
1013
1013
  op << shell_escape(op.pop)
1014
- op.clear
1015
1014
  end
1015
+ op.clear
1016
1016
  when :clear
1017
1017
  if confirm("Remove #{sub_style('all', styles: theme[:active])} stash entries? [y/N] ", 'N')
1018
1018
  source(stdout: true)
@@ -1061,7 +1061,7 @@ module Squared
1061
1061
  { pat: /^(## )(.+?)(\.{3})(.+)$/, styles: [nil, g, nil, r], index: -1 }
1062
1062
  ]
1063
1063
  else
1064
- [{ pat: /^(\t+)([a-z]+: +.+)$/, styles: r, index: 2 }]
1064
+ [pat: /^(\t+)([a-z]+: +.+)$/, styles: r, index: 2]
1065
1065
  end
1066
1066
  end
1067
1067
  out, banner, from = source(io: true)
@@ -1069,7 +1069,7 @@ module Squared
1069
1069
  list_result(ret, 'files', from: from, action: 'modified')
1070
1070
  end
1071
1071
 
1072
- def revbuild(flag = nil, opts = [], sync: invoked_sync?('revbuild', flag), **kwargs)
1072
+ def revbuild(flag = nil, opts = [], sync: nil, **kwargs)
1073
1073
  statusargs = lambda do
1074
1074
  {
1075
1075
  include: relativepath(as_a(kwargs[:include]), all: true),
@@ -1087,6 +1087,7 @@ module Squared
1087
1087
  sha = git_spawn('rev-parse --verify HEAD').chomp
1088
1088
  return if sha.empty?
1089
1089
 
1090
+ sync = invoked_sync?('revbuild', flag) if sync.nil?
1090
1091
  kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? statusargs.call : @revbuild || {}
1091
1092
  case flag
1092
1093
  when :build
@@ -1454,7 +1455,7 @@ module Squared
1454
1455
  else
1455
1456
  git_spawn 'fetch --all --prune --quiet' if option('sync')
1456
1457
  out, banner, from = source(cmd << '-vv --no-abbrev --list', io: true)
1457
- ret = write_lines(out, grep: /^\*\s+#{Regexp.escape(head)}\s/, banner: banner, first: true) do |line|
1458
+ ret = write_lines(out, grep: [/^\*\s+#{Regexp.escape(head)}\s/], banner: banner, first: true) do |line|
1458
1459
  next line if stdin?
1459
1460
 
1460
1461
  data = line.sub(/^\*\s+/, '').split(/\s+/)
@@ -1582,7 +1583,7 @@ module Squared
1582
1583
  op << shell_quote(remote) if remote
1583
1584
  out, banner, from = source(io: true)
1584
1585
  print_item banner
1585
- ret = write_lines(out, grep: op.extras)
1586
+ ret = write_lines(out, grep: op.extras, prefix: "refs/#{flag}/")
1586
1587
  list_result(ret, flag.to_s, from: from, grep: op.extras)
1587
1588
  end
1588
1589
 
@@ -1659,7 +1660,7 @@ module Squared
1659
1660
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
1660
1661
  multiple: false, **kwargs)
1661
1662
  cmd = cmd.target if cmd.is_a?(OptionPartition)
1662
- banner = nil if multiple && banner
1663
+ banner = nil if banner && (multiple || !banner?)
1663
1664
  if cmd.respond_to?(:done)
1664
1665
  if io && banner == false
1665
1666
  from = nil
@@ -1720,15 +1721,27 @@ module Squared
1720
1721
  end
1721
1722
  end
1722
1723
 
1723
- def write_lines(data, banner: nil, loglevel: nil, grep: nil, sub: nil, pass: false, first: false)
1724
- grep &&= as_a(grep).yield_self { |a| a.empty? || a.include?('*') ? nil : a.map { |val| Regexp.new(val) } }
1725
- sub &&= stdin? ? nil : as_a(sub)
1724
+ def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1725
+ grep = unless grep.empty?
1726
+ grep.map do |val|
1727
+ if val.is_a?(Regexp)
1728
+ val
1729
+ else
1730
+ val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
1731
+ Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
1732
+ end
1733
+ end
1734
+ end
1735
+ sub = nil if stdin?
1726
1736
  ret = 0
1727
1737
  out = []
1728
1738
  data.each do |line|
1729
1739
  next if grep&.none? { |pat| pat.match?(line) }
1730
1740
 
1731
- line = yield line if block_given?
1741
+ if block_given?
1742
+ line = yield line
1743
+ next unless line
1744
+ end
1732
1745
  if loglevel
1733
1746
  log&.add loglevel, line
1734
1747
  else
@@ -1746,20 +1759,15 @@ module Squared
1746
1759
  ret
1747
1760
  end
1748
1761
 
1749
- def list_result(size, type, from: nil, action: 'found', grep: nil)
1762
+ def list_result(size, type, grep: [], action: 'found', from: nil)
1750
1763
  if verbose
1751
1764
  if size > 0
1752
1765
  styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
1753
1766
  styles << :bold if styles.size <= 1
1754
1767
  puts print_footer("#{size} #{size == 1 ? type.sub(/(?:(?<!l)e)?s\z/, '') : type}",
1755
- sub: { pat: /^(\d+)(.+)$/, styles: styles })
1768
+ sub: [pat: /^(\d+)(.+)$/, styles: styles])
1756
1769
  else
1757
- puts empty_status("No #{type} were #{action}", 'grep', grep.is_a?(Array) ? case grep.size
1758
- when 0
1759
- nil
1760
- else
1761
- grep.join(', ')
1762
- end : grep.to_s)
1770
+ puts empty_status("No #{type} were #{action}", 'grep', grep.join(', '))
1763
1771
  end
1764
1772
  end
1765
1773
  on :last, from
@@ -1840,7 +1848,7 @@ module Squared
1840
1848
  when 'refspec'
1841
1849
  refspec << shell_escape($2, quote: true)
1842
1850
  end
1843
- elsif op.arg?('--multiple')
1851
+ elsif op.arg?('multiple')
1844
1852
  op.found << opt
1845
1853
  else
1846
1854
  op.errors << opt
@@ -1855,7 +1863,7 @@ module Squared
1855
1863
  op.merge(refspec)
1856
1864
  end
1857
1865
  op.delete('--all')
1858
- elsif op.arg?('--multiple')
1866
+ elsif op.arg?('multiple')
1859
1867
  op.swap.merge(op.map! { |opt| shell_escape(opt, quote: true) })
1860
1868
  return
1861
1869
  elsif option('all')
@@ -1972,7 +1980,7 @@ module Squared
1972
1980
  end
1973
1981
 
1974
1982
  def commithash(val)
1975
- val[/:(\h{5,40})\z/, 1] || val[/\A#\{(\h{5,40})\}\z/, 1]
1983
+ val[/\A:(\h{5,40})\z/, 1] || val[/\A#\{(\h{5,40})\}\z/, 1]
1976
1984
  end
1977
1985
 
1978
1986
  def commithead(val)