squared 0.5.12 → 0.6.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.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
- require 'date'
5
4
  require 'logger'
6
5
 
7
6
  module Squared
@@ -15,15 +14,17 @@ module Squared
15
14
  include Prompt
16
15
  include Utils
17
16
  include Support
17
+ include Workspace::Support::Variables
18
18
  include Rake::DSL
19
19
 
20
- VAR_SET = %i[parent global script index envname desc dependfile dependindex theme archive env dev prod graph
21
- pass only exclude asdf].freeze
20
+ OPTIONS = Workspace::Support.hashobj
21
+ VAR_SET = %i[parent global script index envname desc dependfile dependname dependindex theme archive env graph
22
+ dev prod pass only exclude asdf].freeze
22
23
  BLK_SET = %i[run depend doc lint test copy clean].freeze
23
24
  SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+))?[-.]?(\S+)?\b/.freeze
24
25
  URI_SCHEME = %r{\A([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
25
26
  TASK_METADATA = Rake::TaskManager.record_task_metadata
26
- private_constant :VAR_SET, :BLK_SET, :SEM_VER, :URI_SCHEME, :TASK_METADATA
27
+ private_constant :OPTIONS, :VAR_SET, :BLK_SET, :SEM_VER, :URI_SCHEME, :TASK_METADATA
27
28
 
28
29
  class << self
29
30
  def populate(*); end
@@ -35,13 +36,33 @@ module Squared
35
36
  (%i[build archive graph prereqs] + BLK_SET).freeze
36
37
  end
37
38
 
38
- def as_path(val)
39
- case val
40
- when Pathname
41
- val
42
- when String
43
- Pathname.new(val)
39
+ def options(*args, **kwargs)
40
+ name = nil
41
+ with = []
42
+ proj = []
43
+ opts = []
44
+ args.each do |val|
45
+ case val
46
+ when String
47
+ if name
48
+ opts << val
49
+ else
50
+ name = val
51
+ end
52
+ when Symbol
53
+ if name
54
+ proj << val
55
+ else
56
+ with << val
57
+ end
58
+ end
44
59
  end
60
+ return if !name || (opts.empty? && kwargs.empty? && with.empty?)
61
+
62
+ base = OPTIONS[ref]
63
+ data = [opts.freeze, kwargs.freeze, with.freeze].freeze
64
+ proj << :_ if proj.empty?
65
+ proj.each { |val| (base[val] ||= {})[name.to_s] = data }
45
66
  end
46
67
 
47
68
  def ref
@@ -61,28 +82,39 @@ module Squared
61
82
  def to_s
62
83
  super[/[^:]+\z/, 0]
63
84
  end
85
+
86
+ private
87
+
88
+ def as_path(val)
89
+ case val
90
+ when Pathname
91
+ val
92
+ when String
93
+ Pathname.new(val)
94
+ end
95
+ end
64
96
  end
65
97
 
66
98
  @@tasks = {}
67
99
  @@graph = { _: [] }
68
100
  @@asdf = Pathname.new("#{Dir.home}/.asdf").yield_self do |path|
69
- if path.join('asdf.sh').exist?
70
- [path, 15]
71
- elsif ENV['ASDF_DATA_DIR']
72
- [Pathname.new(ENV['ASDF_DATA_DIR']), 16]
73
- end
101
+ version = if path.join('asdf.sh').exist?
102
+ 15
103
+ elsif ENV['ASDF_DATA_DIR'] && (path = Pathname.new(ENV['ASDF_DATA_DIR'])).exist?
104
+ 16
105
+ end
106
+ Struct.new(:path, :version).new(path, version) if version
74
107
  end
75
108
  @@print_order = 0
76
109
 
77
110
  subtasks({
78
111
  'graph' => %i[run print].freeze,
79
- 'unpack' => %i[zip tar gem ext].freeze,
80
- 'asdf' => %i[set exec current]
112
+ 'unpack' => %i[zip gz tar ext].freeze,
113
+ 'asdf' => %i[set exec current update latest where reshim]
81
114
  })
82
115
 
83
116
  attr_reader :name, :project, :workspace, :path, :theme, :group, :parent, :dependfile,
84
- :exception, :pipe, :verbose
85
- attr_accessor :global
117
+ :exception, :pipe, :verbose, :global
86
118
 
87
119
  def initialize(workspace, path, name, *, group: nil, first: {}, last: {}, error: {}, common: ARG[:COMMON],
88
120
  **kwargs)
@@ -108,20 +140,20 @@ module Squared
108
140
  else
109
141
  val.nil? ? workspace.verbose : val
110
142
  end
143
+ self.global = false
111
144
  @output = []
112
145
  @ref = []
113
146
  @children = []
114
147
  @events = hashobj.update({ first: first, last: last, error: error })
115
148
  @as = hashobj
116
149
  @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
117
- @parent = nil
118
- @global = false
119
150
  @log = nil
120
151
  @dev = nil
121
152
  @prod = nil
122
153
  @withargs = nil
123
154
  @session = nil
124
155
  @index = -1
156
+ parent_set kwargs[:parent]
125
157
  run_set(kwargs[:run], kwargs[:env], opts: kwargs.fetch(:opts, true))
126
158
  graph_set kwargs[:graph]
127
159
  pass_set kwargs[:pass]
@@ -157,32 +189,24 @@ module Squared
157
189
 
158
190
  data = @workspace.script_find(*@ref, @group)
159
191
  if @output[0].nil?
160
- if (scr = data[:script])
192
+ if data[:script]
161
193
  unless kwargs[:script] == false
162
- @global = true
163
- script_set(scr, args: data.fetch(:args, kwargs[:args]), prod: kwargs[:prod])
194
+ script_set(data[:script], args: data.fetch(:args, kwargs[:args]), prod: kwargs[:prod], global: true)
164
195
  end
165
- elsif (run = data[:run])
166
- @global = true
167
- run_set run
196
+ elsif data[:run]
197
+ run_set(data[:run], global: true)
168
198
  end
169
- unless data[:env]
170
- if (scr = kwargs[:script])
171
- @global = false
172
- script_set(scr, args: kwargs[:args])
173
- elsif @script && !data[:global]
174
- if (scr = @script[:script])
175
- @global = false
176
- script_set(scr, args: @script.fetch(:args, kwargs[:args]))
177
- elsif (run = @script[:run])
178
- @global = false
179
- run_set run
180
- end
199
+ if kwargs[:script]
200
+ script_set(kwargs[:script], args: kwargs[:args]) unless data[:env][:script]
201
+ elsif @script
202
+ if @script[:script]
203
+ script_set(@script[:script], args: @script.fetch(:args, kwargs[:args])) unless data[:global][:script]
204
+ elsif @script[:run] && !data[:global][:run]
205
+ run_set @script[:run]
181
206
  end
182
207
  end
183
- elsif data[:env] && data[:run]
184
- @global = true
185
- run_set data[:run]
208
+ elsif data[:run] && data[:env][:run]
209
+ run_set(data[:run], global: true)
186
210
  end
187
211
  end
188
212
 
@@ -196,10 +220,11 @@ module Squared
196
220
  return if @log
197
221
 
198
222
  log = log.is_a?(Hash) ? log.dup : { file: log }
199
- if (file = env('LOG_FILE'))
200
- file = Time.now.strftime(file)
201
- elsif (val = env('LOG_AUTO'))
202
- file = "#{@name}-%s.log" % [case val
223
+ file = if (val = env('LOG_FILE'))
224
+ Time.now.strftime(val)
225
+ elsif (val = env('LOG_AUTO'))
226
+ require 'date'
227
+ "#{@name}-%s.log" % [case val
203
228
  when 'y', 'year'
204
229
  Date.today.year
205
230
  when 'm', 'month'
@@ -209,19 +234,21 @@ module Squared
209
234
  else
210
235
  val.include?('%') ? Time.now.strftime(val) : Time.now.strftime('%FT%T%:z')
211
236
  end]
212
- elsif (val = log[:file])
213
- file = val.is_a?(String) ? Time.now.strftime(val) : "#{@name}-#{Date.today}.log"
214
- end
215
- begin
216
- file &&= @workspace.home.join(env('LOG_DIR', ''), file).realdirpath
217
- rescue StandardError => e
218
- file = nil
219
- print_error e
220
- end
237
+ elsif (val = log[:file])
238
+ if val.is_a?(String)
239
+ Time.now.strftime(val)
240
+ else
241
+ require 'date'
242
+ "#{@name}-#{Date.today}.log"
243
+ end
244
+ end
245
+ .yield_self do |dir|
246
+ @workspace.home.join(env('LOG_DIR', ''), dir).realdirpath if dir
247
+ rescue StandardError => e
248
+ print_error e
249
+ end
221
250
  log[:progname] ||= @name
222
- if (val = env('LOG_LEVEL', ignore: false))
223
- log[:level] = val.match?(/\d/) ? log_sym(val.to_i) : val
224
- end
251
+ log[:level] = val.match?(/\d/) ? log_sym(val.to_i) : val if (val = env('LOG_LEVEL', ignore: false))
225
252
  log.delete(:file)
226
253
  @log = [file, log]
227
254
  end
@@ -229,7 +256,7 @@ module Squared
229
256
  def initialize_env(dev: nil, prod: nil, **)
230
257
  @dev = env_match('BUILD', dev, suffix: 'DEV', strict: true)
231
258
  @prod = env_match('BUILD', prod, suffix: 'PROD', strict: true)
232
- if (val = env('BUILD', suffix: 'ENV')) && @output[2] != false
259
+ if @output[2] != false && (val = env('BUILD', suffix: 'ENV'))
233
260
  @output[2] = parse_json(val, hint: "BUILD_#{@envname}_ENV") || @output[2]
234
261
  end
235
262
  unless @output[0] == false || @output[0].is_a?(Array)
@@ -244,7 +271,6 @@ module Squared
244
271
  @version = val if (val = env('BUILD', suffix: 'VERSION'))
245
272
  return unless (val = env('BUILD', strict: true))
246
273
 
247
- @global = false
248
274
  if val == '0'
249
275
  @output = [false]
250
276
  elsif script?
@@ -326,6 +352,10 @@ module Squared
326
352
  end
327
353
  end
328
354
 
355
+ def global=(val)
356
+ @global = val unless val.nil?
357
+ end
358
+
329
359
  def ref
330
360
  Base.ref
331
361
  end
@@ -353,39 +383,39 @@ module Squared
353
383
  out, done = graph(args, out: [])
354
384
  out.map! do |val|
355
385
  n = done.index { |proj| val.match?(/ #{Regexp.escape(proj.name)}(?:@\d|\z)/) }
356
- n ? "#{val} (#{n.succ})" : val
386
+ n ? val.subhint(n.succ) : val
357
387
  end
358
388
  emphasize(out, title: path, right: true, border: borderstyle, sub: [
359
- { pat: /\A(#{Regexp.escape(path.to_s)})(.*)\z/, styles: theme[:header] },
360
- { pat: /\A(#{Regexp.escape(name)})(.*)\z/, styles: theme[:active] },
361
- { pat: /\A((?~ \() \()(\d+)(\).*)\z/, styles: theme[:inline], index: 2 }
389
+ opt_style(theme[:header], /\A(#{Regexp.escape(path.to_s)})(.*)\z/),
390
+ opt_style(theme[:active], /\A(#{Regexp.escape(name)})(.*)\z/),
391
+ opt_style(theme[:inline], /\A((?~ \() \()(\d+)(\).*)\z/, 2)
362
392
  ])
363
393
  end
364
394
  end
365
395
  when 'unpack'
366
- format_desc(action, flag, 'tag/url,dir,digest?,f|force?', before: flag == :ext ? 'ext' : nil)
396
+ format_desc(action, flag, 'tag/url,dir,digest?,f/orce?', before: ('ext' if flag == :ext))
367
397
  params = %i[tag dir digest force]
368
- params.prepend(:ext) if flag == :ext
398
+ params.unshift(:ext) if flag == :ext
369
399
  task flag, params do |_, args|
370
400
  ext = flag == :ext ? param_guard(action, flag, args: args, key: :ext) : flag.to_s
371
401
  tag = param_guard(action, flag, args: args, key: :tag)
372
402
  dir = param_guard(action, flag, args: args, key: :dir)
373
403
  unless tag.match?(URI_SCHEME)
374
- if flag == :gem
375
- tag = "https://rubygems.org/downloads/#{File.basename(tag, '.gem')}.gem"
376
- elsif @release
377
- tag = "%s.#{ext}" % [@release.include?('??') ? @release.sub('??', tag) : @release + tag]
378
- else
379
- raise_error("no base uri: #{tag}", hint: ext)
380
- end
381
- end
382
- case (digest = args.digest)
383
- when 'f', 'force'
384
- digest = nil
385
- force = true
386
- else
387
- force = args.fetch(:force, false)
404
+ tag = if ext == 'gem'
405
+ "https://rubygems.org/downloads/#{File.basename(tag, '.gem')}.gem"
406
+ elsif @release
407
+ "%s.#{ext}" % [@release.include?('??') ? @release.sub('??', tag) : @release + tag]
408
+ else
409
+ raise_error ArgumentError, "no base uri: #{tag}", hint: ext
410
+ end
388
411
  end
412
+ force = case (digest = args.digest)
413
+ when 'f', 'force'
414
+ digest = nil
415
+ true
416
+ else
417
+ args.fetch(:force, false)
418
+ end
389
419
  unpack(basepath(dir), uri: tag, digest: digest, ext: ext, force: force)
390
420
  end
391
421
  when 'asdf'
@@ -393,7 +423,7 @@ module Squared
393
423
 
394
424
  case flag
395
425
  when :set
396
- format_desc action, flag, 'version,opts*=u|home,p|parent'
426
+ format_desc action, flag, 'version,dir?=u/home|p/arent'
397
427
  task flag, [:version] do |_, args|
398
428
  args = if (version = args.version)
399
429
  args.extras
@@ -402,15 +432,15 @@ module Squared
402
432
  @asdf[1].children
403
433
  .map(&:basename)
404
434
  .sort { |a, b| b <=> a }
405
- .append('latest', 'system'),
406
- force: true, accept: [['Confirm?', false, true]],
407
- values: ['Options'])
435
+ .push('latest', 'system'),
436
+ accept: [accept_y('Confirm?')],
437
+ values: 'Options', force: true)
408
438
  OptionPartition.strip(opts)
409
439
  end
410
440
  asdf(flag, args, version: version)
411
441
  end
412
442
  else
413
- format_desc(action, flag, flag == :exec ? 'command' : nil)
443
+ format_desc(action, flag, ('command' if flag == :exec))
414
444
  task flag do |_, args|
415
445
  args = args.to_a
416
446
  args << readline('Enter command', force: true) if args.empty? && flag == :exec
@@ -429,7 +459,7 @@ module Squared
429
459
  end
430
460
 
431
461
  def with(**kwargs, &blk)
432
- @withargs = kwargs.empty? ? nil : kwargs
462
+ @withargs = (kwargs unless kwargs.empty?)
433
463
  if block_given?
434
464
  instance_eval(&blk)
435
465
  @withargs = nil
@@ -450,7 +480,6 @@ module Squared
450
480
  kwargs = hashdup(@withargs).update(kwargs) if @withargs
451
481
  kwargs[:group] = group if group && !kwargs.key?(:group)
452
482
  kwargs[:ref] = ref unless kwargs.key?(:ref)
453
- parent = self
454
483
  proj = nil
455
484
  name = case name
456
485
  when String, Symbol
@@ -458,8 +487,7 @@ module Squared
458
487
  else
459
488
  path.basename
460
489
  end
461
- workspace.add(path, name, **kwargs) do
462
- __send__ :parent_set, parent
490
+ workspace.add(path, name, parent: self, **kwargs) do
463
491
  proj = self
464
492
  instance_eval(&blk) if block_given?
465
493
  end
@@ -475,14 +503,14 @@ module Squared
475
503
 
476
504
  def inject(obj, *args, **kwargs, &blk)
477
505
  if enabled?
478
- out = obj.link(self, *args, **kwargs, &blk) if obj.respond_to?(:link)
479
- if !out
480
- print_error('link not compatible', subject: obj, hint: name)
481
- elsif out.respond_to?(:build)
482
- out.build
483
- end
506
+ raise 'link not compatible' unless obj.respond_to?(:link) && (out = obj.link(self, *args, **kwargs, &blk))
507
+
508
+ out.build if out.respond_to?(:build)
484
509
  end
485
510
  self
511
+ rescue StandardError => e
512
+ print_error(e, subject: obj, hint: name)
513
+ self
486
514
  end
487
515
 
488
516
  def build(*args, sync: invoked_sync?('build'), from: :run, **)
@@ -490,8 +518,8 @@ module Squared
490
518
  if args.empty?
491
519
  return unless from == :run
492
520
 
493
- banner = verbosetype > 1 if from_base?('build')
494
- run_b(@run, sync: sync, from: from, banner: banner) if series?(@run)
521
+ banner = verbose? if from_base?('build')
522
+ run_b(@run, sync: sync, banner: banner, from: from) if series?(@run)
495
523
  args = @output
496
524
  end
497
525
  if args.first.is_a?(Struct)
@@ -529,12 +557,12 @@ module Squared
529
557
  if cmd
530
558
  return run_b(cmd, sync: sync, from: from) if cmd.is_a?(Proc) || cmd.is_a?(Method)
531
559
 
532
- cmd = as_get(cmd, from)
560
+ cmd = as_get cmd, from
533
561
  opts = compose(opts, script: false) if opts && respond_to?(:compose)
534
562
  flags = append_hash(flags, target: []).join(' ') if flags.is_a?(Hash)
535
563
  case opts
536
564
  when Hash
537
- cmd = Array(cmd).append(flags)
565
+ cmd = Array(cmd).push(flags)
538
566
  .concat(append_hash(opts, target: [], build: true))
539
567
  .compact
540
568
  .join(' ')
@@ -551,7 +579,7 @@ module Squared
551
579
  cmd = compose(as_get(opts, from), flags, script: true, args: extra, from: from)
552
580
  from = :script if from == :run && script?
553
581
  end
554
- run(cmd, var, sync: sync, from: from, banner: banner)
582
+ run(cmd, var, sync: sync, banner: banner, from: from)
555
583
  end
556
584
 
557
585
  def depend(*, sync: invoked_sync?('depend'), **)
@@ -564,7 +592,7 @@ module Squared
564
592
  next if @@graph[:_].include?(proj)
565
593
 
566
594
  if (val = ENV["PREREQS_#{proj.instance_variable_get(:@envname)}"] || ENV["PREREQS_#{proj.ref.upcase}"])
567
- split_escape(val).each do |meth|
595
+ split_escape(val) do |meth|
568
596
  if proj.respond_to?(meth.to_sym)
569
597
  begin
570
598
  proj.__send__(meth, sync: sync)
@@ -651,9 +679,9 @@ module Squared
651
679
  end
652
680
 
653
681
  def graph(start = [], tasks = nil, *, sync: invoked_sync?('graph'), pass: [], out: nil, **)
654
- if (val = env('GRAPH', strict: true))
682
+ env('GRAPH', strict: true) do |val|
655
683
  tasks ||= []
656
- split_escape(val).each do |task|
684
+ split_escape(val) do |task|
657
685
  if ref?(task.to_sym) && (script = workspace.script_get(:graph, ref: task.to_sym))
658
686
  tasks.concat(script[:graph])
659
687
  else
@@ -661,7 +689,7 @@ module Squared
661
689
  end
662
690
  end
663
691
  end
664
- pass.concat(split_escape(val)) if (val = env('GRAPH', suffix: 'PASS'))
692
+ env('GRAPH', suffix: 'PASS') { |val| pass.concat(split_escape(val)) }
665
693
  start, neg = start.partition { |name| !name.start_with?('-') }
666
694
  data = graph_collect(self, start, pass: neg.map! { |name| name[1..-1] })
667
695
  unless out
@@ -684,9 +712,9 @@ module Squared
684
712
  if !target.exist?
685
713
  target.mkpath
686
714
  elsif !target.directory?
687
- raise_error('invalid location', hint: target)
715
+ raise_error Errno::EEXIST, target, hint: uri
688
716
  elsif !file && !target.empty?
689
- raise_error('directory not empty', hint: target) unless force || env('UNPACK_FORCE')
717
+ raise_error Errno::EEXIST, target, hint: uri unless force || env('UNPACK_FORCE')
690
718
  create = true
691
719
  end
692
720
  if digest
@@ -706,25 +734,27 @@ module Squared
706
734
  when 128, 'sha512'
707
735
  Digest::SHA512
708
736
  else
709
- raise_error "invalid checksum: #{digest}"
737
+ raise_error "invalid checksum: #{digest}", hint: uri
710
738
  end
711
739
  end
712
- if (val = env('HEADERS')) && (val = parse_json(val, hint: "HEADERS_#{@envname}"))
713
- headers = headers.is_a?(Hash) ? headers.merge(val) : val
740
+ env('HEADERS') do |val|
741
+ if (data = parse_json(val, hint: "HEADERS_#{@envname}"))
742
+ headers = headers.is_a?(Hash) ? headers.merge(data) : data
743
+ end
714
744
  end
715
745
  if file
716
746
  ext ||= File.extname(file)[1..-1]
717
747
  else
718
748
  require 'open-uri'
719
749
  data = nil
720
- (uri = Array(uri)).each_with_index do |url, index|
750
+ (uri = Array(uri)).each_with_index do |url, i|
721
751
  URI.open(url, headers) do |f|
722
752
  data = f.read
723
753
  if algo && algo.hexdigest(data) != digest
724
754
  data = nil
725
- raise_error("checksum failed: #{digest}", hint: url) if index == uri.size - 1
755
+ raise_error "invalid checksum: #{digest}", hint: url if i == uri.size.pred
726
756
  end
727
- next if ext && index == 0
757
+ next if ext && i == 0
728
758
 
729
759
  case f.content_type
730
760
  when 'application/zip'
@@ -740,7 +770,7 @@ module Squared
740
770
  break uri = url if data
741
771
  end
742
772
  unless data && (ext ||= URI.parse(uri).path[/\.(\w+)(?:\?|\z)/, 1])
743
- raise_error("no content#{data ? ' type' : ''}", hint: uri)
773
+ raise_error(data ? TypeError : RuntimeError, "no content#{data ? ' type' : ''}", hint: uri)
744
774
  end
745
775
  end
746
776
  ext = ext.downcase
@@ -749,13 +779,13 @@ module Squared
749
779
  end
750
780
  begin
751
781
  unless file
752
- if ext == 'gem'
753
- dir = Dir.mktmpdir
754
- file = File.new(File.join(dir, File.basename(uri)), 'w')
755
- else
756
- require 'tempfile'
757
- file = Tempfile.new("#{name}-")
758
- end
782
+ file = if ext == 'gem'
783
+ dir = Dir.mktmpdir
784
+ File.new(File.join(dir, File.basename(uri)), 'w')
785
+ else
786
+ require 'tempfile'
787
+ Tempfile.new("#{name}-")
788
+ end
759
789
  file.write(data)
760
790
  file.close
761
791
  file = Pathname.new(file)
@@ -769,7 +799,7 @@ module Squared
769
799
  case ext
770
800
  when 'zip', 'aar'
771
801
  session 'unzip', shell_quote(file), quote_option('d', target)
772
- when 'tar', 'tgz', 'txz', 'tar.gz', 'tar.xz', 'gz', 'xz'
802
+ when 'tar', /\A(?:t|tar\.)?[gx]z\z/
773
803
  flags = +(verbose ? 'v' : '')
774
804
  if ext.end_with?('gz')
775
805
  flags += 'z'
@@ -812,16 +842,17 @@ module Squared
812
842
  def asdf(flag, opts = [], version: nil)
813
843
  return unless @asdf
814
844
 
815
- cmd = session 'asdf', flag
845
+ cmd = flag == :update ? session('asdf', 'plugin update') : session('asdf', flag)
816
846
  name = @asdf.first
817
- legacy = @@asdf[1] == 15
847
+ legacy = @@asdf.version == 15
848
+ banner = true
818
849
  case flag
819
850
  when :set
820
- u = has_value?(opts, %w[u home])
851
+ u = has_value?(opts, 'u', 'home')
821
852
  cmd << if legacy
822
853
  cmd.delete(flag)
823
854
  u ? 'global' : 'local'
824
- elsif has_value?(opts, %w[p parent])
855
+ elsif has_value?(opts, 'p', 'parent')
825
856
  '--parent'
826
857
  elsif u
827
858
  '--home'
@@ -832,8 +863,11 @@ module Squared
832
863
  when :current
833
864
  cmd << '--no-header' unless legacy
834
865
  cmd << name
866
+ else
867
+ cmd << name
868
+ banner = flag == :update || flag == :reshim
835
869
  end
836
- print_success if success?(run(from: :"asdf:#{flag}"), flag == :set)
870
+ success?(run(banner: banner, from: :"asdf:#{flag}"), flag == :set || flag == :reshim)
837
871
  end
838
872
 
839
873
  def first(key, *args, **kwargs, &blk)
@@ -873,28 +907,27 @@ module Squared
873
907
  instance_variable_set :"@#{key}", [blk]
874
908
  end
875
909
  else
876
- log&.warn "series: @#{key} (invalid)"
910
+ log&.warn "series: @#{key}".subhint('invalid')
877
911
  end
878
912
  self
879
913
  end
880
914
 
881
- def run(cmd = @session, var = nil, exception: self.exception, sync: true, from: nil, banner: true, chdir: path,
915
+ def run(cmd = @session, var = nil, exception: self.exception, sync: true, banner: true, from: nil, chdir: path,
882
916
  interactive: nil, hint: nil, series: true, **)
883
- unless cmd
884
- print_error('no command session started', subject: project, hint: from, pass: true)
885
- return
886
- end
917
+ return print_error('no command session started', subject: project, hint: from, pass: true) unless cmd
918
+
887
919
  cmd = cmd.target if cmd.is_a?(OptionPartition)
888
- if interactive && (!@session || !option('y'))
889
- title, y = case interactive
890
- when Array
891
- interactive
892
- when String
893
- [interactive, 'N']
894
- else
895
- ['Run', 'Y']
896
- end
897
- exit 1 unless confirm("#{title}? [#{sub_style(cmd.to_s, styles: theme[:inline])}]", y)
920
+ if interactive && sync && (!@session || !option('y'))
921
+ title, y, hint = case interactive
922
+ when Array
923
+ interactive
924
+ when String
925
+ [interactive, 'N']
926
+ else
927
+ ['Run', 'Y']
928
+ end
929
+ title = "#{title} #{sub_style(hint, styles: theme[:active])}" if hint
930
+ exit 1 unless confirm_basic("#{title}?", cmd, y)
898
931
  end
899
932
  cmd = session_done cmd
900
933
  log&.info cmd
@@ -904,7 +937,7 @@ module Squared
904
937
  log&.warn "ENV discarded: #{var}" if var
905
938
  task_invoke(cmd, exception: exception, warning: warning?)
906
939
  else
907
- print_item(format_banner(hint ? "#{cmd} (#{hint})" : cmd, banner: banner), series: series) if sync
940
+ print_item(format_banner(cmd, banner: banner, hint: hint), series: series) if sync
908
941
  if var != false && (pre = runenv)
909
942
  case pre
910
943
  when Hash
@@ -936,14 +969,7 @@ module Squared
936
969
  args = block_args args, &blk
937
970
  end
938
971
  if variables.include?(key) || blocks.include?(key)
939
- val = case args.size
940
- when 0
941
- nil
942
- when 1
943
- args.first
944
- else
945
- args
946
- end
972
+ val = args.size > 1 ? args : args.first
947
973
  case key
948
974
  when :index
949
975
  index_set val
@@ -973,7 +999,7 @@ module Squared
973
999
  instance_variable_set(:"@#{key}", val)
974
1000
  end
975
1001
  else
976
- log&.warn "variable_set: @#{key} (private)"
1002
+ log&.warn "variable_set: @#{key}".subhint('private')
977
1003
  end
978
1004
  self
979
1005
  end
@@ -996,6 +1022,10 @@ module Squared
996
1022
  @ref.include?(val)
997
1023
  end
998
1024
 
1025
+ def exist?(*args)
1026
+ basepath(*args).exist?
1027
+ end
1028
+
999
1029
  def build?
1000
1030
  !!@output[0] || script? || series?(@run)
1001
1031
  end
@@ -1069,10 +1099,14 @@ module Squared
1069
1099
  @dependindex ? @dependindex.succ : 0
1070
1100
  end
1071
1101
 
1102
+ def dependname
1103
+ @dependname ||= dependfile&.basename.to_s
1104
+ end
1105
+
1072
1106
  def log
1073
1107
  return @log unless @log.is_a?(Array)
1074
1108
 
1075
- @log = Logger.new(enabled? ? @log.first : nil, **@log.last)
1109
+ @log = Logger.new((@log.first if enabled?), **@log.last)
1076
1110
  end
1077
1111
 
1078
1112
  def allref
@@ -1099,6 +1133,12 @@ module Squared
1099
1133
  workspace.task_localname(name)
1100
1134
  end
1101
1135
 
1136
+ def scriptname(from: :run)
1137
+ return unless (name = @output[1]) && respond_to?(:compose)
1138
+
1139
+ as_get name, from
1140
+ end
1141
+
1102
1142
  def inspect
1103
1143
  "#<#{self.class}: #{name} => #{self}>"
1104
1144
  end
@@ -1117,10 +1157,18 @@ module Squared
1117
1157
  log_console(*args, pipe: kwargs[:pipe] || pipe)
1118
1158
  end
1119
1159
 
1120
- def run_s(*cmd, env: nil, sync: true, from: nil, banner: verbose != false, **kwargs)
1160
+ def run_s(*cmd, sync: true, banner: verbosetype > 0, from: nil, **kwargs)
1161
+ cmd.flatten!
1162
+ case cmd.last
1163
+ when Hash, TrueClass, FalseClass
1164
+ var = cmd.pop
1165
+ end
1121
1166
  on :first, from
1122
1167
  begin
1123
- cmd.flatten.each { |val| run(val, env, sync: sync, banner: banner, **kwargs) }
1168
+ cmd.each do |val|
1169
+ print_run val, banner
1170
+ run(val, var, sync: sync, banner: banner, **kwargs)
1171
+ end
1124
1172
  rescue StandardError => e
1125
1173
  ret = on :error, from, e
1126
1174
  raise unless ret == true
@@ -1137,7 +1185,12 @@ module Squared
1137
1185
  when Proc
1138
1186
  instance_eval(&obj)
1139
1187
  when Method
1140
- obj.call
1188
+ args = if (n = obj.arity.abs) > 0
1189
+ Array.new(n).tap { |data| data[0] = self }
1190
+ else
1191
+ []
1192
+ end
1193
+ obj.call(*args)
1141
1194
  else
1142
1195
  if series?(obj)
1143
1196
  obj.each(&:call)
@@ -1153,7 +1206,6 @@ module Squared
1153
1206
  single: false, last: false, context: nil)
1154
1207
  tag = ->(proj) { "#{proj.name}#{SEM_VER.match?(proj.version) ? "@#{proj.version}" : ''}" }
1155
1208
  script = ->(proj) { workspace.script_get(:graph, group: proj.group, ref: proj.allref)&.fetch(:graph, nil) }
1156
- check = ->(deps) { deps.reject { |val| done.include?(val) } }
1157
1209
  dedupe = lambda do |name|
1158
1210
  next [] unless (ret = data[name])
1159
1211
 
@@ -1164,12 +1216,11 @@ module Squared
1164
1216
  end
1165
1217
  ret
1166
1218
  end
1167
- start = target.name
1168
1219
  if depth == 0
1169
- items = check.call(dedupe.call(start))
1220
+ items = dedupe.call(target.name) - done
1170
1221
  single = items.size == 1
1171
1222
  else
1172
- items = check.call(data[start])
1223
+ items = data[target.name] - done
1173
1224
  end
1174
1225
  if out
1175
1226
  a, b, c, d, e = ARG[:GRAPH]
@@ -1184,21 +1235,21 @@ module Squared
1184
1235
  "#{last ? d : c}#{b * 3}#{e} #{f}"
1185
1236
  end
1186
1237
  else
1187
- "#{single ? ' ' : a}#{' ' * (depth - 1)}#{last ? d : c}#{b * 3}#{items.empty? ? b : e} #{f}"
1238
+ "#{single ? ' ' : a}#{' ' * depth.pred}#{last ? d : c}#{b * 3}#{items.empty? ? b : e} #{f}"
1188
1239
  end
1189
1240
  end
1190
1241
  items.each_with_index do |proj, i|
1191
1242
  next if done.include?(proj)
1192
1243
 
1193
- t = dedupe.call(proj.name)
1244
+ t = dedupe.call(name = proj.name)
1194
1245
  j = if out
1195
- if i == items.size - 1 || check.call(post = items[(i + 1)..-1]).empty?
1246
+ if i == items.size.pred || (post = items[i.succ..-1] - done).empty?
1196
1247
  true
1197
1248
  elsif !t.empty? && depth > 0
1198
- post.reject { |pr| t.include?(pr) }.empty?
1249
+ (post - t).empty?
1199
1250
  end
1200
1251
  end
1201
- unless start == proj.name || (none = check.call(t).empty?)
1252
+ unless target.name == name || (none = (t - done).empty?)
1202
1253
  graph_branch(proj, data, tasks, out, sync: sync, pass: pass, done: done, depth: depth.succ,
1203
1254
  single: single, last: j == true, context: target)
1204
1255
  end
@@ -1206,28 +1257,28 @@ module Squared
1206
1257
  (tasks || (subtasks = script.call(proj)) || (dev? ? %w[build copy] : %w[depend build])).each do |meth|
1207
1258
  next if pass.include?(meth)
1208
1259
 
1209
- if workspace.task_defined?(cmd = task_join(proj.name, meth))
1210
- if ENV.key?(key = "BANNER_#{proj.name.upcase}")
1260
+ if workspace.task_defined?(cmd = task_join(name, meth))
1261
+ if ENV.key?(key = "BANNER_#{name.upcase}")
1211
1262
  key = nil
1212
1263
  else
1213
1264
  ENV[key] = '0'
1214
1265
  end
1215
1266
  run(cmd, sync: false, banner: false)
1216
1267
  ENV.delete(key) if key
1217
- elsif proj.has?(meth, tasks || subtasks ? nil : workspace.baseref)
1268
+ elsif proj.has?(meth, (workspace.baseref unless tasks || subtasks))
1218
1269
  proj.__send__(meth.to_sym, sync: sync)
1219
1270
  end
1220
1271
  end
1221
1272
  elsif none
1222
1273
  a, b, c, d = ARG[:GRAPH]
1223
1274
  out << if depth == 0
1224
- "#{i == items.size - 1 ? d : c}#{b * 4} #{tag.call(proj)}"
1275
+ "#{i == items.size.pred ? d : c}#{b * 4} #{tag.call(proj)}"
1225
1276
  else
1226
1277
  s = ''.dup
1227
1278
  k = 0
1228
1279
  final = data.keys.last
1229
1280
  while k < depth
1230
- indent = k > 0 ? ((last && !j) || (j && k == depth - 1) || single) : j && last && depth == 1
1281
+ indent = k > 0 ? ((last && !j) || (j && k == depth.pred) || single) : j && last && depth == 1
1231
1282
  s += "#{indent || (last && data[final].last == context) ? ' ' : a} "
1232
1283
  k += 1
1233
1284
  end
@@ -1245,22 +1296,19 @@ module Squared
1245
1296
  next if pass.include?(val)
1246
1297
 
1247
1298
  if (obj = workspace.find(name: val))
1248
- next unless obj.enabled?
1249
-
1250
- items = [obj]
1299
+ obj.enabled? ? [obj] : []
1251
1300
  else
1252
- items = workspace.find(group: val, ref: val.to_sym)
1253
- end
1254
- items.each do |proj|
1301
+ workspace.find(group: val, ref: val.to_sym)
1302
+ end.each do |proj|
1255
1303
  next if pass.include?(name = proj.name)
1256
1304
 
1257
1305
  if proj.graph? && !data.key?(name) && !root.include?(name)
1258
1306
  graph_collect(proj, data: data, pass: pass, root: root + [name, target.name])
1259
1307
  end
1260
- next if (objs = data.fetch(name, [])).include?(target)
1261
-
1262
- deps << proj
1263
- deps.concat(objs)
1308
+ unless (objs = data.fetch(name, [])).include?(target)
1309
+ deps << proj
1310
+ deps.concat(objs)
1311
+ end
1264
1312
  end
1265
1313
  end
1266
1314
  deps.uniq!
@@ -1286,44 +1334,96 @@ module Squared
1286
1334
  end
1287
1335
 
1288
1336
  def env(key, default = nil, suffix: nil, equals: nil, ignore: nil, strict: false)
1289
- a = "#{key}_#{@envname}"
1337
+ name = "#{key}_#{@envname}"
1290
1338
  ret = if suffix
1291
- ENV.fetch("#{a}_#{suffix}", '')
1339
+ ENV.fetch("#{name}_#{suffix}", '')
1292
1340
  elsif strict
1293
- ENV[a].to_s
1341
+ ENV[name].to_s
1294
1342
  else
1295
1343
  ignore = ['0'].freeze if ignore.nil?
1296
- ENV[a] || ENV.fetch(key, '')
1344
+ ENV[name] || ENV.fetch(key, '')
1297
1345
  end
1298
- return ret == equals.to_s unless equals.nil?
1299
-
1300
- ret.empty? || (ignore && Array(ignore).any? { |val| ret == val.to_s }) ? default : ret
1346
+ if !equals.nil?
1347
+ ret = ret == equals.to_s
1348
+ elsif ret.empty? || (ignore && Array(ignore).any? { |val| ret == val.to_s })
1349
+ ret = default
1350
+ end
1351
+ block_given? && !ret.nil? ? yield(ret) : ret
1301
1352
  end
1302
1353
 
1303
1354
  def session(*cmd, prefix: cmd.first, main: true, path: true, options: true)
1304
- prefix = stripext prefix.to_s
1355
+ prefix = prefix.to_s.stripext
1305
1356
  if path && (val = shell_bin(prefix))
1306
1357
  cmd[0] = shell_quote(val, force: false)
1307
1358
  end
1308
1359
  ret = JoinSet.new(cmd.flatten(1))
1309
- if options && (val = env("#{prefix.upcase}_OPTIONS"))
1310
- split_escape(val).each { |opt| ret.last(fill_option(opt), /\A(--?[^ =]+)[ =].+\z/m) }
1360
+ if options
1361
+ env("#{prefix.upcase}_OPTIONS") do |val|
1362
+ if val.start_with('-')
1363
+ ret.concat(shell_parse(val))
1364
+ else
1365
+ split_escape(val) { |opt| ret.last(fill_option(opt), /\A(--?[^= ]+)[= ].+\z/m) }
1366
+ end
1367
+ end
1311
1368
  end
1312
1369
  main ? @session = ret : ret
1313
1370
  end
1314
1371
 
1315
- def session_delete(*args, target: @session)
1316
- OptionPartition.delete(target, *args)
1317
- end
1318
-
1319
1372
  def session_output(*cmd, **kwargs)
1320
1373
  session(*cmd, main: false, options: false, **kwargs)
1321
1374
  end
1322
1375
 
1376
+ def session_get(val, pass: nil)
1377
+ base = OPTIONS[ref]
1378
+ args = []
1379
+ kwargs = {}
1380
+ with = []
1381
+ [:_, name.to_sym].each do |key|
1382
+ next unless base.key?(key) && (a, b, c = base[key][val])
1383
+
1384
+ args.concat(a)
1385
+ append_keys(kwargs, b, :opts)
1386
+ with.concat(c)
1387
+ end
1388
+ OptionPartition.uniq!(args, pass) if pass
1389
+ [args, kwargs, with]
1390
+ end
1391
+
1392
+ def session_apply(val, args: nil, kwargs: nil, pass: [], keys: [:opts], exclude: [])
1393
+ a = []
1394
+ b = {}
1395
+ Array(val).each do |c|
1396
+ d, e, f = session_get c
1397
+ unless (f -= exclude).empty?
1398
+ h = []
1399
+ i = {}
1400
+ session_apply(f.map!(&:to_s), args: h, kwargs: i, pass: pass, keys: keys, exclude: exclude.concat(f))
1401
+ a.concat(h)
1402
+ append_keys(b, i, *keys)
1403
+ end
1404
+ a.concat(d)
1405
+ append_keys(b, e, *keys)
1406
+ end
1407
+ if args
1408
+ args.unshift(*a)
1409
+ OptionPartition.uniq!(args, pass) if pass
1410
+ end
1411
+ kwargs&.update(b) { |_, val| val }
1412
+ nil
1413
+ end
1414
+
1415
+ def session_opts(val, args: nil, kwargs: nil, pass: nil, keys: [:opts])
1416
+ opts = kwargs.delete(:opts) || []
1417
+ return opts unless val
1418
+
1419
+ session_apply(val, args: args, kwargs: kwargs, pass: pass, keys: keys)
1420
+ kwargs.fetch(:opts, []).concat(opts)
1421
+ end
1422
+
1323
1423
  def session_done(cmd)
1324
- return cmd unless cmd.respond_to?(:done)
1424
+ return cmd.to_s unless cmd.respond_to?(:done)
1325
1425
 
1326
- raise_error('no args added', hint: cmd.first) unless cmd.size > 1
1426
+ raise_error 'no command added', hint: cmd.first unless cmd.size > 1
1327
1427
  @session = nil if cmd == @session
1328
1428
  cmd.done
1329
1429
  end
@@ -1338,20 +1438,22 @@ module Squared
1338
1438
  return unless prefix
1339
1439
 
1340
1440
  args.each do |val|
1341
- next unless (ret = env(env_key(stripext(prefix), val), **kwargs))
1441
+ next unless (ret = env(env_key(prefix.to_s.stripext, val), **kwargs))
1342
1442
 
1343
1443
  return block_given? ? yield(ret) : ret
1344
1444
  end
1345
1445
  nil
1346
1446
  end
1347
1447
 
1348
- def option_clear(opts, target: @session, **kwargs)
1448
+ def option_clear(opts, empty = true, target: @session, **kwargs)
1349
1449
  return unless target
1350
1450
 
1351
1451
  OptionPartition.clear(target, opts, styles: theme[:inline], **kwargs)
1452
+ opts.clear if empty
1453
+ nil
1352
1454
  end
1353
1455
 
1354
- def print_success(*)
1456
+ def print_success
1355
1457
  puts 'Success'
1356
1458
  end
1357
1459
 
@@ -1359,10 +1461,17 @@ module Squared
1359
1461
  warn log_message(loglevel, *args, **kwargs) if warning?
1360
1462
  end
1361
1463
 
1464
+ def print_run(cmd, banner = true, verbose: nil, **)
1465
+ return if banner || !stdout? || verbose == false
1466
+
1467
+ puts "\n> #{cmd}"
1468
+ printsucc
1469
+ end
1470
+
1362
1471
  def print_item(*val, series: true)
1363
1472
  puts unless printfirst?
1364
1473
  printsucc if series
1365
- puts val unless val.empty? || (val.size == 1 && val.first.nil?)
1474
+ puts val unless val.empty? || (val.size == 1 && !val.first)
1366
1475
  end
1367
1476
 
1368
1477
  def print_banner(*lines, client: false, styles: theme[:banner], border: borderstyle, **)
@@ -1391,14 +1500,15 @@ module Squared
1391
1500
 
1392
1501
  def print_footer(*lines, sub: nil, reverse: false, right: false, border: borderstyle, **)
1393
1502
  n = line_width lines
1503
+ sub = as_a sub
1394
1504
  lines.map! do |val|
1395
1505
  s = right ? val.rjust(n) : val.ljust(n)
1396
- sub&.each { |h| s = sub_style(s, **h) }
1506
+ sub.each { |h| s = sub_style(s, **h) }
1397
1507
  s
1398
1508
  end
1399
- ret = [sub_style(ARG[:BORDER][1] * n, styles: border)].concat(lines)
1400
- ret.reverse! if reverse
1401
- ret.join("\n")
1509
+ [sub_style(ARG[:BORDER][1] * n, styles: border)].concat(lines)
1510
+ .tap { |ret| ret.reverse! if reverse }
1511
+ .join("\n")
1402
1512
  end
1403
1513
 
1404
1514
  def print_status(*args, from: nil, **kwargs)
@@ -1407,15 +1517,15 @@ module Squared
1407
1517
  case from
1408
1518
  when :outdated
1409
1519
  out = print_footer("major #{args[0]} / minor #{args[1]} / patch #{args[2]}", right: true).split("\n")
1410
- out[1] = sub_style(out[1], pat: /^( +major )(\d+)(.+)$/, styles: theme[:major], index: 2)
1411
- out[1] = sub_style(out[1], pat: /^(.+)(minor )(\d+)(.+)$/, styles: theme[:active], index: 3)
1520
+ out[1] = sub_style(out[1], **opt_style(theme[:major], /^( +major )(\d+)(.+)$/, 2))
1521
+ out[1] = sub_style(out[1], **opt_style(theme[:active], /^(.+)(minor )(\d+)(.+)$/, 3))
1522
+ out[1] = sub_style(out[1], **opt_style(theme[:current], /^(.+)(patch )(\d+)(.+)$/, 3)) if theme[:current]
1412
1523
  puts out
1413
1524
  when :completed
1414
- if verbose && kwargs[:start]
1415
- msg = sub_style('completed', styles: theme[:active])
1416
- puts log_message(Logger::INFO, *args, msg, subject: kwargs[:subject],
1417
- hint: time_format(time_epoch - kwargs[:start]))
1418
- end
1525
+ return unless verbose && kwargs[:start]
1526
+
1527
+ puts log_message(Logger::INFO, *args, sub_style('completed', styles: theme[:active]),
1528
+ subject: kwargs[:subject], hint: time_format(time_epoch - kwargs[:start]))
1419
1529
  end
1420
1530
  end
1421
1531
 
@@ -1425,7 +1535,7 @@ module Squared
1425
1535
  workspace.format_desc([@desc, action, flag].compact, opts, **kwargs)
1426
1536
  end
1427
1537
 
1428
- def format_banner(cmd, banner: true)
1538
+ def format_banner(cmd, banner: true, hint: nil, strip: nil)
1429
1539
  return unless banner && banner?
1430
1540
 
1431
1541
  if (data = workspace.banner_get(*@ref, group: group))
@@ -1439,11 +1549,14 @@ module Squared
1439
1549
  out = []
1440
1550
  if data.command
1441
1551
  if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+))( |\z)/
1442
- path = $3 || $2 || $1
1443
- name = stripext path
1444
- cmd = cmd.sub(path, data.command == 0 ? name : name.upcase)
1552
+ arg = $3 || $2 || $1
1553
+ cmd = cmd.sub(arg, data.command == 0 ? arg.stripext : arg.stripext.upcase)
1554
+ end
1555
+ if strip || (strip.nil? && data.order.include?(:path))
1556
+ cmd = cmd.gsub(/(?:#{s = Regexp.escape(File.join(path, ''))}?(?=["'])|#{s})/, '')
1557
+ .gsub(/(?: -[^ ])? (?:""|'')/, '')
1445
1558
  end
1446
- out << cmd
1559
+ out << cmd.subhint(hint)
1447
1560
  end
1448
1561
  data.order.each do |val|
1449
1562
  if val.is_a?(Array)
@@ -1471,7 +1584,7 @@ module Squared
1471
1584
  end
1472
1585
  end
1473
1586
 
1474
- def format_list(items, cmd, type, grep: [], from: nil, each: nil)
1587
+ def format_list(items, cmd, type, grep: [], from: nil)
1475
1588
  reg = grep.map { |val| Regexp.new(val) }
1476
1589
  out = []
1477
1590
  unless items.empty?
@@ -1479,29 +1592,29 @@ module Squared
1479
1592
  items.each_with_index do |val, i|
1480
1593
  next unless matchany?(val.first, reg)
1481
1594
 
1482
- out << ('%*d. %s' % [pad, i.succ, each ? each.call(val) : val.first])
1595
+ out << ('%*d. %s' % [pad, i.succ, block_given? ? yield(val) : val.first])
1483
1596
  end
1484
1597
  end
1485
1598
  sub = [headerstyle]
1486
- if out.empty?
1487
- out = ["No #{type} were found:", '']
1488
- unless grep.empty?
1489
- i = 0
1490
- out.concat(grep.map { |s| "#{i += 1}. #{s}" })
1491
- out << ''
1492
- end
1493
- if from
1494
- out << (from = from.to_s)
1495
- pat = /\A(#{Regexp.escape(from)})(.*)\z/
1496
- end
1497
- else
1498
- pat = /\A(\s*\d+\.)(.+)\z/
1499
- unless grep.empty?
1500
- footer = "#{out.size} found "
1501
- sub << { pat: /\A(\d+)( .+)\z/, styles: theme[:inline] }
1502
- end
1503
- end
1504
- sub << { pat: pat, styles: theme[:active] } if pat
1599
+ pat = if out.empty?
1600
+ out = ["No #{type} were found:", '']
1601
+ unless grep.empty?
1602
+ i = 0
1603
+ out.concat(grep.map { |s| "#{i += 1}. #{s}" })
1604
+ out << ''
1605
+ end
1606
+ if from
1607
+ out << (from = from.to_s)
1608
+ /\A(#{Regexp.escape(from)})(.*)\z/
1609
+ end
1610
+ else
1611
+ unless grep.empty?
1612
+ footer = "#{out.size} found "
1613
+ sub << opt_style(theme[:inline], /\A(\d+)( .+)\z/)
1614
+ end
1615
+ /\A(\s*\d+\.)(.+)\z/
1616
+ end
1617
+ sub << opt_style(theme[:active], pat) if pat
1505
1618
  emphasize(out, title: task_join(name, cmd), border: borderstyle, sub: sub, footer: footer, right: true)
1506
1619
  end
1507
1620
 
@@ -1515,8 +1628,7 @@ module Squared
1515
1628
 
1516
1629
  def append_hash(data, target: @session || [], build: false)
1517
1630
  if build && (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
1518
- type = "__#{type}__"
1519
- if (extra = data[type] || data[type.to_sym]).is_a?(Hash)
1631
+ if (extra = data[type = "__#{type}__"] || data[type.to_sym]).is_a?(Hash)
1520
1632
  data = data.merge(extra)
1521
1633
  else
1522
1634
  extra = nil
@@ -1537,30 +1649,24 @@ module Squared
1537
1649
  when FalseClass
1538
1650
  target << shell_option(key).sub(/^--(?!no-)/, '--no-')
1539
1651
  else
1540
- target << shell_option(key, val.is_a?(String) ? val : nil)
1652
+ target << shell_option(key, (val if val.is_a?(String)))
1541
1653
  end
1542
1654
  end
1543
1655
  target
1544
1656
  end
1545
1657
 
1546
1658
  def append_any(val, target: @session, build: false, delim: false)
1547
- return unless val
1548
-
1549
1659
  if delim && !target.include?('--')
1550
1660
  target << '--'
1551
1661
  else
1552
1662
  delim = false
1553
1663
  end
1554
- val = shell_split(val) if val.is_a?(String)
1664
+ val = shell_split val if val.is_a?(String)
1555
1665
  case val
1556
1666
  when Hash
1557
1667
  append_hash(val, target: target, build: build)
1558
1668
  when Enumerable
1559
- if target.is_a?(Array)
1560
- target.concat(val.to_a)
1561
- else
1562
- target.merge(val.to_a)
1563
- end
1669
+ merge_list target, val
1564
1670
  else
1565
1671
  target.delete('--') if delim
1566
1672
  nil
@@ -1581,7 +1687,7 @@ module Squared
1581
1687
  next unless (val = option(opt, **kwargs))
1582
1688
 
1583
1689
  return target << if flag
1584
- shell_option(opt, equals ? val : nil, quote: quote, escape: escape, force: force)
1690
+ shell_option(opt, (val if equals), quote: quote, escape: escape, force: force)
1585
1691
  else
1586
1692
  shell_quote val
1587
1693
  end
@@ -1601,9 +1707,11 @@ module Squared
1601
1707
  flag = "no-#{flag}"
1602
1708
  val = nil
1603
1709
  end
1604
- ret << shell_option(flag, equals ? val : nil, escape: escape, quote: quote, force: force)
1710
+ ret << shell_option(flag, (val if equals), escape: escape, quote: quote, force: force)
1605
1711
  end
1606
- ret.each { |val| target << val } unless ret.empty?
1712
+ next if ret.empty?
1713
+
1714
+ merge_list target, ret
1607
1715
  end
1608
1716
  end
1609
1717
 
@@ -1611,6 +1719,24 @@ module Squared
1611
1719
  target << '--no-color' if !ARG[:COLOR] || stdin? || option('color', target: target, equals: '0')
1612
1720
  end
1613
1721
 
1722
+ def append_keys(base, data, *keys)
1723
+ out = {}
1724
+ keys.each do |key|
1725
+ next unless data.key?(key)
1726
+
1727
+ out[key] = case (val = data[key])
1728
+ when Array
1729
+ base.fetch(key, []) + val
1730
+ when Hash
1731
+ base.fetch(key, {}).update(val)
1732
+ else
1733
+ val
1734
+ end
1735
+ end
1736
+ base.update(data)
1737
+ .update(out)
1738
+ end
1739
+
1614
1740
  def merge_opts(base, data)
1615
1741
  case data
1616
1742
  when String
@@ -1651,20 +1777,31 @@ module Squared
1651
1777
  end
1652
1778
  end
1653
1779
 
1654
- def collect_hash(data, pass: [])
1655
- [].tap do |ret|
1656
- data.each { |key, val| ret.concat(val) unless pass.include?(key) }
1780
+ def merge_list(base, data)
1781
+ data = Array(data)
1782
+ case base
1783
+ when Array
1784
+ base.concat(data)
1785
+ when Set
1786
+ base.merge(data)
1787
+ else
1788
+ Array(base).concat(data)
1657
1789
  end
1658
1790
  end
1659
1791
 
1792
+ def collect_hash(data, pass: [])
1793
+ ret = []
1794
+ data.each { |key, val| ret.concat(val) unless pass.include?(key) }
1795
+ ret
1796
+ end
1797
+
1660
1798
  def parse_json(val, kind: Hash, hint: nil)
1661
1799
  ret = JSON.parse(val)
1662
- raise_error("invalid JSON #{kind.name}", val, hint: hint) if kind && !ret.is_a?(kind)
1800
+ raise_error 'invalid JSON'.subhint(kind.name), val, hint: hint if kind && !ret.is_a?(kind)
1801
+ ret
1663
1802
  rescue StandardError => e
1664
1803
  log&.warn e
1665
1804
  print_error(e, subject: name)
1666
- else
1667
- ret
1668
1805
  end
1669
1806
 
1670
1807
  def param_guard(action, flag, args:, key: nil, pat: nil, values: nil)
@@ -1676,12 +1813,16 @@ module Squared
1676
1813
  raise_error(action, "#{flag}[#{key}]", hint: val.nil? ? 'missing' : 'invalid')
1677
1814
  elsif args.is_a?(Array) && args.empty?
1678
1815
  @session = nil
1679
- raise_error(action, "#{flag}+", hint: 'empty')
1816
+ raise_error action, "#{flag}+", hint: 'empty'
1680
1817
  end
1681
1818
  args
1682
1819
  end
1683
1820
 
1684
- def confirm_outdated(pkg, ver, rev, cur = nil, lock: false, col1: 0)
1821
+ def confirm_basic(msg, target, default = 'Y', style: :inline, **kwargs)
1822
+ confirm("#{msg} [#{sub_style(target.to_s, styles: theme[style])}]", default, **kwargs)
1823
+ end
1824
+
1825
+ def confirm_outdated(pkg, ver, rev, cur = nil, lock: false, col1: 0, **kwargs)
1685
1826
  a = sub_style(case rev
1686
1827
  when 1
1687
1828
  'MAJOR'
@@ -1693,16 +1834,17 @@ module Squared
1693
1834
  b = sub_style(pkg.ljust(col1), styles: theme[:inline])
1694
1835
  c = lock ? sub_style((cur || 'locked').rjust(7), styles: color(:red)) : cur&.rjust(7)
1695
1836
  d = rev == 1 || lock ? 'N' : 'Y'
1696
- confirm "#{a}: #{b}#{c} #{sub_style(ver.rjust(col1 > 0 ? 10 : 0), styles: theme[:inline])} ", d
1837
+ confirm("#{a}: #{b}#{c} #{sub_style(ver.rjust(col1 > 0 ? 10 : 0), styles: theme[:inline])} ", d, **kwargs)
1697
1838
  end
1698
1839
 
1699
1840
  def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil, multiple: false,
1700
1841
  force: true, **kwargs)
1701
- puts if !series && !printfirst?
1702
- msg = "#{msg} (optional)" unless force
1703
- unless (ret = choice(msg, list, multiple: multiple, force: force, **kwargs)) && !ret.empty?
1842
+ puts unless series || printfirst?
1843
+ ret = choice(msg, list, multiple: multiple, force: force, **kwargs).tap do |val|
1844
+ next unless val.to_s.empty?
1845
+
1704
1846
  exit 1 if force
1705
- return
1847
+ return nil
1706
1848
  end
1707
1849
  ret = multiple ? ret.map! { |val| val.sub(trim, '') } : ret.sub(trim, '') if trim
1708
1850
  if column
@@ -1716,7 +1858,7 @@ module Squared
1716
1858
  ret = Array(ret) if accept.any? { |val| val[1] == true }
1717
1859
  loop do
1718
1860
  item = accept.first
1719
- c = confirm("#{item[0]}#{hint ? " [#{hint}]" : ''}", item[2] ? 'Y' : 'N', timeout: 60)
1861
+ c = confirm("#{item[0]}#{hint ? " [#{hint}]" : ''}", item[2] ? 'Y' : 'N')
1720
1862
  if item[1] == true
1721
1863
  ret << c
1722
1864
  elsif !c
@@ -1737,25 +1879,29 @@ module Squared
1737
1879
  force = false
1738
1880
  end
1739
1881
  val = readline(val, force: force)
1740
- ret << (val.empty? ? nil : val)
1882
+ ret << (val unless val.empty?)
1741
1883
  end
1742
1884
  end
1743
1885
  printsucc unless series
1744
1886
  ret
1745
1887
  end
1746
1888
 
1889
+ def accept_b(val, yes = false)
1890
+ [val, true, yes]
1891
+ end
1892
+
1893
+ def accept_y(val, bool = false)
1894
+ [val, bool, true]
1895
+ end
1896
+
1747
1897
  def command_args(args, min: 0, force: false, **kwargs)
1748
1898
  return if args.size > min || option('i', 'interactive', **kwargs, equals: '0')
1749
1899
 
1750
1900
  readline('Enter arguments', force: force)
1751
1901
  end
1752
1902
 
1753
- def block_args(val = nil, &blk)
1754
- if (ret = instance_eval(&blk)).nil?
1755
- val
1756
- else
1757
- Array(ret)
1758
- end
1903
+ def block_args(fallback = nil, &blk)
1904
+ (ret = instance_eval(&blk)).nil? ? fallback : Array(ret)
1759
1905
  end
1760
1906
 
1761
1907
  def runenv
@@ -1788,12 +1934,10 @@ module Squared
1788
1934
 
1789
1935
  def matchmap(list, prefix = nil)
1790
1936
  list.map do |val|
1791
- if val.is_a?(Regexp)
1792
- val
1793
- else
1794
- val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
1795
- Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
1796
- end
1937
+ next val if val.is_a?(Regexp)
1938
+
1939
+ val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
1940
+ Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
1797
1941
  end
1798
1942
  end
1799
1943
 
@@ -1810,7 +1954,8 @@ module Squared
1810
1954
  end
1811
1955
 
1812
1956
  def semscan(val, fill: true)
1813
- val.scan(SEM_VER).first.yield_self { |data| fill ? semver(data) : data }
1957
+ ret = val.scan(SEM_VER).first
1958
+ fill ? semver(ret) : ret
1814
1959
  end
1815
1960
 
1816
1961
  def semcmp(val, other)
@@ -1826,14 +1971,38 @@ module Squared
1826
1971
  end
1827
1972
  [c[0], c[2], c[4] || '0', d]
1828
1973
  end
1829
- a.each_with_index do |c, index|
1830
- next if c == (d = b[index])
1974
+ a.each_with_index do |c, i|
1975
+ next if c == (d = b[i])
1831
1976
 
1832
1977
  return c.to_i < d.to_i ? 1 : -1
1833
1978
  end
1834
1979
  0
1835
1980
  end
1836
1981
 
1982
+ def sembump(val, flag = :patch, join: true)
1983
+ ret = semscan(val, fill: false)
1984
+ case flag
1985
+ when :major
1986
+ ret[2] = if ret[0] != '0' || ret[2].nil?
1987
+ ret[0] = ret[0].succ
1988
+ '0'
1989
+ else
1990
+ ret[2].succ
1991
+ end
1992
+ ret[4] = '0'
1993
+ when :minor
1994
+ if ret[0] == '0'
1995
+ ret[4] &&= ret[4].succ
1996
+ else
1997
+ ret[2] = ret[2].succ
1998
+ ret[4] &&= '0'
1999
+ end
2000
+ when :patch
2001
+ ret[4] &&= ret[4].succ
2002
+ end
2003
+ join ? ret.join : ret
2004
+ end
2005
+
1837
2006
  def semgte?(val, other)
1838
2007
  semcmp(val, other) != 1
1839
2008
  end
@@ -1843,7 +2012,7 @@ module Squared
1843
2012
  end
1844
2013
 
1845
2014
  def indexerror(val, list = nil)
1846
- raise_error("requested index #{val}", hint: list && "of #{list.size}")
2015
+ raise_error IndexError, "requested index #{val}", hint: list && "of #{list.size}"
1847
2016
  end
1848
2017
 
1849
2018
  def indexchar
@@ -1862,10 +2031,6 @@ module Squared
1862
2031
  val.compact.flat_map { |s| color(s) }
1863
2032
  end
1864
2033
 
1865
- def epochtime
1866
- Time.now.strftime('%s%L').to_i
1867
- end
1868
-
1869
2034
  def verbosetype
1870
2035
  case verbose
1871
2036
  when TrueClass
@@ -1897,7 +2062,7 @@ module Squared
1897
2062
  end
1898
2063
  end
1899
2064
 
1900
- def on_error(err, from, exception: self.exception, pass: false, dryrun: false)
2065
+ def on_error(err, from, pass: false, exception: self.exception, dryrun: false)
1901
2066
  log&.error err
1902
2067
  unless dryrun
1903
2068
  ret = on :error, from, err
@@ -1906,20 +2071,17 @@ module Squared
1906
2071
  print_error(err, pass: pass) unless ret
1907
2072
  end
1908
2073
 
1909
- def pwd_set(pass: false, dryrun: false, from: nil)
2074
+ def pwd_set(pass: false, exception: self.exception, dryrun: false, from: nil)
1910
2075
  pwd = Dir.pwd
1911
2076
  return yield if (path.to_s == pwd || pass == true) && (workspace.mri? || !workspace.windows?)
1912
2077
 
1913
2078
  Dir.chdir path
1914
- ret = yield
1915
- Dir.chdir pwd
2079
+ yield.tap { Dir.chdir pwd }
1916
2080
  rescue StandardError => e
1917
- on_error(e, from, dryrun: dryrun)
1918
- else
1919
- ret
2081
+ on_error(e, from, exception: exception, dryrun: dryrun)
1920
2082
  end
1921
2083
 
1922
- def run_set(cmd, val = nil, opts: nil, **)
2084
+ def run_set(cmd, val = nil, opts: nil, global: false, **)
1923
2085
  noopt = @output[1] == false && !@output[0].nil?
1924
2086
  noenv = @output[2] == false
1925
2087
  parse = lambda do |data|
@@ -1938,6 +2100,7 @@ module Squared
1938
2100
  ret[2] = data[:env] unless dise
1939
2101
  ret
1940
2102
  end
2103
+ self.global = global
1941
2104
  case cmd
1942
2105
  when Array
1943
2106
  @output = if cmd.all? { |data| data.is_a?(Hash) }
@@ -1969,11 +2132,12 @@ module Squared
1969
2132
  end
1970
2133
  end
1971
2134
 
1972
- def script_set(cmd, prod: nil, args: nil, **)
2135
+ def script_set(cmd, prod: nil, args: nil, global: false, **)
1973
2136
  return if @output[1] == false && @output[0].nil?
1974
2137
 
2138
+ self.global = global
1975
2139
  @output[0] = nil
1976
- @output[1] = if @global && cmd.is_a?(Array)
2140
+ @output[1] = if self.global && cmd.is_a?(Array)
1977
2141
  cmd[prod == true ? 1 : 0]
1978
2142
  else
1979
2143
  cmd
@@ -2018,7 +2182,7 @@ module Squared
2018
2182
 
2019
2183
  def asdf_set(val)
2020
2184
  @asdf = if @@asdf && val
2021
- dir = @@asdf[0].join('installs', val)
2185
+ dir = @@asdf.path.join('installs', val)
2022
2186
  [val, dir] if dir.exist? && !dir.empty?
2023
2187
  end
2024
2188
  end
@@ -2034,9 +2198,12 @@ module Squared
2034
2198
  end
2035
2199
 
2036
2200
  def dependfile_set(list)
2037
- @dependindex = list.index { |file| basepath(file).exist? }.tap do |index|
2038
- @dependfile = basepath(list[index || 0])
2039
- end
2201
+ @dependindex = if @dependname
2202
+ @dependfile = basepath @dependname
2203
+ list.index(@dependname)
2204
+ else
2205
+ list.index { |file| exist?(file) }.tap { |i: 0| @dependfile = basepath(list[i]) }
2206
+ end
2040
2207
  end
2041
2208
 
2042
2209
  def as_get(val, from)
@@ -2078,12 +2245,10 @@ module Squared
2078
2245
  end
2079
2246
 
2080
2247
  def checkdir?(val)
2081
- if val.directory? && !val.empty?
2082
- true
2083
- else
2084
- log&.warn "directory \"#{val}\" (#{val.directory? ? 'empty' : 'not found'})"
2085
- false
2086
- end
2248
+ return true if val.directory? && !val.empty?
2249
+
2250
+ log&.warn "directory \"#{val}\"".subhint(val.directory? ? 'empty' : 'missing')
2251
+ false
2087
2252
  end
2088
2253
 
2089
2254
  def semmajor?(cur, want)
@@ -2123,14 +2288,24 @@ module Squared
2123
2288
  return true if val || from_sync?(ac = workspace.task_name(action))
2124
2289
  return val if group && !(val = from_sync?(ac, group)).nil?
2125
2290
  return val if (base = workspace.find_base(self)) && !(val = from_sync?(ac, base.ref)).nil?
2126
- return false if workspace.series.chain?(val = task_join(name, action))
2127
- return true if task_invoked?(val) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
2291
+ return false if workspace.series.chain?(key = task_join(name, action))
2292
+ return true if task_invoked?(key) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
2128
2293
 
2129
- workspace.series.name_get(action).yield_self { |name| name != action && invoked_sync?(name) }
2294
+ ret = workspace.series.name_get(action)
2295
+ ret != action && invoked_sync?(ret)
2130
2296
  end
2131
2297
 
2132
- def success?(ret, display = true)
2133
- ret == true && display && stdout? && banner?
2298
+ def success?(run, *cond)
2299
+ case run
2300
+ when TrueClass
2301
+ true
2302
+ when FalseClass
2303
+ false
2304
+ else
2305
+ $?.success?
2306
+ end.tap do |ret|
2307
+ print_success if ret && stdout? && banner? && cond.none? { |val| val == false }
2308
+ end
2134
2309
  end
2135
2310
 
2136
2311
  def banner?
@@ -2157,12 +2332,13 @@ module Squared
2157
2332
  workspace.warning
2158
2333
  end
2159
2334
 
2160
- def has_value?(data, other)
2161
- case data
2335
+ def has_value?(target, *args)
2336
+ args = args.first if args.size == 1 && args.first.is_a?(Enumerable)
2337
+ case target
2162
2338
  when Hash
2163
- other.is_a?(Enumerable) ? other.any? { |obj| data.value?(obj) } : data.value?(other)
2339
+ args.is_a?(Enumerable) ? args.any? { |obj| target.value?(obj) } : target.value?(args)
2164
2340
  when Enumerable
2165
- other.is_a?(Enumerable) ? other.any? { |obj| data.include?(obj) } : data.include?(other)
2341
+ args.is_a?(Enumerable) ? args.any? { |obj| target.include?(obj) } : target.include?(args)
2166
2342
  else
2167
2343
  false
2168
2344
  end
@@ -2176,28 +2352,16 @@ module Squared
2176
2352
  BLK_SET
2177
2353
  end
2178
2354
 
2179
- def hashobj
2180
- Workspace::Support.hashobj
2181
- end
2182
-
2183
- def hashlist
2184
- Workspace::Support.hashlist
2185
- end
2186
-
2187
- def hashdup
2188
- Workspace::Support.hashdup
2189
- end
2190
-
2191
2355
  def borderstyle
2192
2356
  workspace.banner_get(*@ref, group: group)&.border || theme[:border]
2193
2357
  end
2194
2358
 
2195
2359
  def headerstyle
2196
- { pat: /^(\S+)(\s+)$/, styles: theme[:header] }
2360
+ opt_style theme[:header], /^(\S+)(\s+)$/
2197
2361
  end
2198
2362
 
2199
2363
  def scriptargs
2200
- { target: script? ? @output[1] : @output[0], ref: ref, group: group, global: @global }
2364
+ { target: script? ? @output[1] : @output[0], script: script?, ref: ref, group: group, global: @global }
2201
2365
  end
2202
2366
  end
2203
2367