squared 0.6.9 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ require 'json'
4
4
  require 'logger'
5
5
 
6
6
  require_relative 'support/class'
7
+ require_relative 'support/utils'
7
8
 
8
9
  module Squared
9
10
  module Workspace
@@ -16,12 +17,13 @@ module Squared
16
17
  include Prompt
17
18
  include Utils
18
19
  include Support
20
+ include Support::Utils
19
21
  include Workspace::Support::Variables
20
22
  include Rake::DSL
21
23
 
22
24
  OPTIONS = Workspace::Support.hashobj
23
25
  VAR_SET = %i[parent global script index envname desc dependfile dependname dependindex theme archive env graph
24
- dev prod pass only exclude asdf].freeze
26
+ dev prod timeout pass only exclude asdf].freeze
25
27
  BLK_SET = %i[run depend doc lint test copy clean].freeze
26
28
  SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+))?[-.]?(\S+)?\b/.freeze
27
29
  URI_SCHEME = %r{\A([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
@@ -99,33 +101,35 @@ module Squared
99
101
 
100
102
  @@tasks = {}
101
103
  @@graph = { _: [] }
102
- @@asdf = Pathname.new("#{Dir.home}/.asdf").yield_self do |path|
104
+ @@asdf = Pathname.new(ENV['ASDF_DIR'] || "#{Dir.home}/.asdf").yield_self do |path|
103
105
  version = if path.join('asdf.sh').exist?
104
106
  15
105
107
  elsif ENV['ASDF_DATA_DIR'] && (path = Pathname.new(ENV['ASDF_DATA_DIR'])).exist?
106
108
  16
107
109
  end
108
- Struct.new(:path, :version).new(path, version) if version
110
+ if version
111
+ config = ENV['ASDF_CONFIG_FILE'] || File.join(Dir.home, '.asdfrc')
112
+ Struct.new(:path, :version, :config).new(path, version, config)
113
+ end
109
114
  end
110
115
  @@print_order = 0
111
116
 
112
117
  subtasks({
113
118
  'graph' => %i[run print].freeze,
114
119
  'unpack' => %i[zip gz tar ext],
115
- 'asdf' => %i[set exec current update latest where reshim]
120
+ 'asdf' => %i[set exec env current update latest where reshim]
116
121
  })
117
122
 
118
123
  attr_reader :name, :workspace, :path, :theme, :group, :parent, :children, :dependfile,
119
- :exception, :pipe, :verbose, :global
124
+ :exception, :pipe, :verbose
120
125
  attr_accessor :project
121
126
 
122
- def initialize(workspace, path, name, *, group: nil, first: {}, last: {}, error: {}, common: ARG[:COMMON],
123
- **kwargs)
127
+ def initialize(workspace, path, name, *, first: {}, last: {}, error: {}, common: ARG[:COMMON], **kwargs)
124
128
  @path = path
125
129
  @workspace = workspace
126
130
  @name = name.to_s.freeze
127
131
  @project = @path.basename.to_s.freeze
128
- @group = group&.to_s.freeze
132
+ @group = kwargs[:group]&.to_s.freeze
129
133
  @envname = env_key(@name).freeze
130
134
  @depend = kwargs[:depend]
131
135
  @doc = kwargs[:doc]
@@ -143,22 +147,28 @@ module Squared
143
147
  else
144
148
  val.nil? ? workspace.verbose : val
145
149
  end
146
- self.global = false
150
+ @global = 0
147
151
  @output = []
148
152
  @ref = []
149
153
  @children = []
150
- @events = hashobj.update({ first: first, last: last, error: error })
151
154
  @as = hashobj
152
155
  @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
156
+ @on = {
157
+ event: hashobj.update({ first: first, last: last, error: error }),
158
+ from: []
159
+ }
160
+ @timeout = { _: [{}.compare_by_identity, nil, 0] }
153
161
  @log = nil
154
162
  @dev = nil
155
163
  @prod = nil
156
164
  @withargs = nil
157
165
  @session = nil
158
166
  @index = -1
167
+ @initargs = kwargs.freeze
159
168
  parent_set kwargs[:parent]
160
169
  run_set(kwargs[:run], kwargs[:env], opts: kwargs.fetch(:opts, true))
161
170
  graph_set kwargs[:graph]
171
+ timeout_set kwargs[:timeout]
162
172
  pass_set kwargs[:pass]
163
173
  only_set kwargs[:only]
164
174
  exclude_set kwargs[:exclude]
@@ -168,117 +178,6 @@ module Squared
168
178
  initialize_ref Base.ref
169
179
  end
170
180
 
171
- def initialize_ref(ref)
172
- @ref << ref unless @exclude.include?(ref)
173
- end
174
-
175
- def initialize_build(ref, **kwargs)
176
- initialize_ref ref
177
- if (@script = @workspace.script_get(group: @group, ref: ref))
178
- if @script[:log] && !kwargs.key?(:log)
179
- kwargs[:log] = @script[:log]
180
- @log = nil
181
- end
182
- @depend = @script[:depend] if @depend.nil?
183
- @doc = @script[:doc] if @doc.nil?
184
- @lint = @script[:lint] if @lint.nil?
185
- @test = @script[:test] if @test.nil?
186
- @clean = @script[:clean] if @clean.nil?
187
- @exclude = @script[:exclude] if @exclude.empty? && @script.key?(:exclude)
188
- end
189
- initialize_events(ref, **kwargs)
190
- initialize_logger(**kwargs)
191
- return if @output[0] == false
192
-
193
- data = @workspace.script_find(*@ref, @group)
194
- if @output[0].nil?
195
- if data[:script]
196
- unless kwargs[:script] == false
197
- script_set(data[:script], args: data.fetch(:args, kwargs[:args]), prod: kwargs[:prod], global: true)
198
- end
199
- elsif data[:run]
200
- run_set(data[:run], global: true)
201
- end
202
- if kwargs[:script]
203
- script_set(kwargs[:script], args: kwargs[:args]) unless data[:env][:script]
204
- elsif @script
205
- if @script[:script]
206
- script_set(@script[:script], args: @script.fetch(:args, kwargs[:args])) unless data[:global][:script]
207
- elsif @script[:run] && !data[:global][:run]
208
- run_set @script[:run]
209
- end
210
- end
211
- elsif data[:run] && data[:env][:run]
212
- run_set(data[:run], global: true)
213
- end
214
- end
215
-
216
- def initialize_events(ref, **)
217
- return unless (events = @workspace.events_get(group: @group, ref: ref))
218
-
219
- events.each { |task, data| data.each { |ev, blk| @events[ev][task] ||= [blk] } }
220
- end
221
-
222
- def initialize_logger(log: nil, **)
223
- return if @log
224
-
225
- log = log.is_a?(Hash) ? log.dup : { file: log }
226
- file = if (val = env('LOG_FILE'))
227
- Time.now.strftime(val)
228
- elsif (val = env('LOG_AUTO'))
229
- require 'date'
230
- "#{@name}-%s.log" % [case val
231
- when 'y', 'year'
232
- Date.today.year
233
- when 'm', 'month'
234
- Date.today.strftime('%Y-%m')
235
- when 'd', 'day', '1'
236
- Date.today
237
- else
238
- val.include?('%') ? Time.now.strftime(val) : Time.now.strftime('%FT%T%:z')
239
- end]
240
- elsif (val = log[:file])
241
- if val.is_a?(String)
242
- Time.now.strftime(val)
243
- else
244
- require 'date'
245
- "#{@name}-#{Date.today}.log"
246
- end
247
- end
248
- .yield_self do |dir|
249
- @workspace.home.join(env('LOG_DIR', ''), dir).realdirpath if dir
250
- rescue StandardError => e
251
- print_error e
252
- end
253
- log[:progname] ||= @name
254
- env('LOG_LEVEL', ignore: false) { |val| log[:level] = val.start_with?(/\d/) ? log_sym(val.to_i) : val }
255
- log.delete(:file)
256
- @log = [file, log]
257
- end
258
-
259
- def initialize_env(dev: nil, prod: nil, **)
260
- @dev = env_match('BUILD', dev, suffix: 'DEV', strict: true)
261
- @prod = env_match('BUILD', prod, suffix: 'PROD', strict: true)
262
- env('BUILD', suffix: 'ENV') { |val| @output[2] = val if (val = parse_json(val)) } unless @output[0] == false
263
- unless @output[0] == false || @output[0].is_a?(Array)
264
- env('BUILD', suffix: 'OPTS') do |val|
265
- n = @output[0] ? 1 : 3
266
- @output[n] = merge_opts(@output[n], shell_split(val))
267
- end
268
- env(ref.to_s.upcase, suffix: 'OPTS') { |val| @output[4] = merge_opts(@output[4], shell_split(val)) }
269
- end
270
- env('BUILD', suffix: 'VERSION') { |val| self.version = val }
271
- env('BUILD', strict: true) do |val|
272
- if val == '0'
273
- @output = [false]
274
- elsif script?
275
- script_set val
276
- else
277
- run_set val
278
- end
279
- end
280
- end
281
-
282
181
  def ==(other)
283
182
  equal?(other)
284
183
  end
@@ -312,10 +211,10 @@ module Squared
312
211
  -1
313
212
  elsif f.any? { |val| e.include?(val) } # rubocop:disable Lint/DuplicateBranch
314
213
  1
315
- elsif @index >= 0 && (i = other.instance_variable_get(:@index)) >= 0
214
+ elsif @index >= 0 && (i = other.index_get) >= 0
316
215
  @index <=> i
317
216
  end
318
- rescue StandardError => e
217
+ rescue => e
319
218
  log&.debug e
320
219
  nil
321
220
  end
@@ -326,7 +225,7 @@ module Squared
326
225
 
327
226
  def exception=(val)
328
227
  @exception = case val
329
- when Numeric, TrueClass, FalseClass
228
+ when Numeric, true, false
330
229
  val
331
230
  else
332
231
  workspace.exception
@@ -344,7 +243,7 @@ module Squared
344
243
 
345
244
  def verbose=(val)
346
245
  @verbose = case val
347
- when Numeric, TrueClass, FalseClass
246
+ when Numeric, true, false
348
247
  val
349
248
  else
350
249
  workspace.verbose
@@ -352,7 +251,17 @@ module Squared
352
251
  end
353
252
 
354
253
  def global=(val)
355
- @global = val unless val.nil?
254
+ return if @global == -1
255
+
256
+ @global = if val == 0
257
+ 0
258
+ elsif val.nil?
259
+ -1
260
+ elsif val < 0
261
+ @global ^ val.abs
262
+ else
263
+ @global | val
264
+ end
356
265
  end
357
266
 
358
267
  def ref
@@ -375,7 +284,7 @@ module Squared
375
284
 
376
285
  format_desc action, flag, '(-)project*'
377
286
  task flag do |_, args|
378
- args = args.to_a.reject { |val| name == val }
287
+ args = args.to_a.reject { |val| val == name }
379
288
  if flag == :run
380
289
  graph args
381
290
  else
@@ -434,10 +343,12 @@ module Squared
434
343
  asdf(flag, args, version: version)
435
344
  end
436
345
  else
437
- format_desc(action, flag, ('command' if flag == :exec))
346
+ format_desc(action, flag, case flag when :exec, :env then 'command' end)
438
347
  task flag do |_, args|
439
348
  args = args.to_a
440
- args << readline('Enter command', force: true) if args.empty? && flag == :exec
349
+ if args.empty? && (flag == :exec || flag == :env)
350
+ args << readline('Enter command', force: flag == :exec)
351
+ end
441
352
  asdf flag, args
442
353
  end
443
354
  end
@@ -453,7 +364,7 @@ module Squared
453
364
  end
454
365
 
455
366
  def with(**kwargs, &blk)
456
- @withargs = (kwargs unless kwargs.empty?)
367
+ @withargs = kwargs.empty? ? nil : kwargs
457
368
  if block_given?
458
369
  instance_eval(&blk)
459
370
  @withargs = nil
@@ -474,18 +385,17 @@ module Squared
474
385
  kwargs = hashdup(@withargs).update(kwargs) if @withargs
475
386
  kwargs[:group] = group if group && !kwargs.key?(:group)
476
387
  kwargs[:ref] = ref unless kwargs.key?(:ref)
477
- proj = nil
478
388
  name = case name
479
389
  when String, Symbol
480
390
  name.to_s
481
391
  else
482
392
  path.basename
483
393
  end
394
+ target = children
484
395
  workspace.add(path, name, parent: self, **kwargs) do
485
- proj = self
396
+ target << self
486
397
  instance_eval(&blk) if block_given?
487
398
  end
488
- children << proj
489
399
  end
490
400
  self
491
401
  end
@@ -502,8 +412,8 @@ module Squared
502
412
  out.build if out.respond_to?(:build)
503
413
  end
504
414
  self
505
- rescue StandardError => e
506
- print_error(e, subject: obj, hint: name)
415
+ rescue => e
416
+ print_error(e, subject: name, hint: obj)
507
417
  self
508
418
  end
509
419
 
@@ -522,9 +432,8 @@ module Squared
522
432
  return unless args.first
523
433
  end
524
434
  if args.all? { |val| val.is_a?(Array) }
525
- cmd = []
526
435
  var = {}
527
- args.each do |val|
436
+ cmd = args.each_with_object([]) do |val, out|
528
437
  case val.first
529
438
  when Proc
530
439
  instance_exec(*val[1..-1], &val.first)
@@ -542,21 +451,20 @@ module Squared
542
451
  end
543
452
  d = append_hash(d, target: []).join(' ') if d.is_a?(Hash)
544
453
  if a
545
- cmd << [replace_bin(a), d, b].compact.join(' ')
454
+ out << [replace_bin(as_get(a, from, 1)), d, b].compact.join(' ')
546
455
  else
547
456
  next unless respond_to?(:compose)
548
457
 
549
- cmd << a if (a = compose(as_get(b, from), d, script: true, args: e, from: from))
458
+ out << a if (a = compose(as_get(b, from, 2), d, script: true, args: e, from: from))
550
459
  end
551
460
  var.update(c) if c.is_a?(Hash)
552
- end
553
- cmd = cmd.join(' && ')
461
+ end.join(' && ')
554
462
  else
555
463
  cmd, opts, var, flags, extra = args
556
464
  if cmd
557
465
  return run_b(cmd, sync: sync, from: from) if cmd.is_a?(Proc) || cmd.is_a?(Method)
558
466
 
559
- cmd = replace_bin as_get(cmd, from)
467
+ cmd = replace_bin as_get(cmd, from, 1)
560
468
  opts = compose(opts, script: false) if opts && respond_to?(:compose)
561
469
  flags = append_hash(flags, target: []).join(' ') if flags.is_a?(Hash)
562
470
  cmd = case opts
@@ -574,7 +482,7 @@ module Squared
574
482
  else
575
483
  return unless (opts || extra) && respond_to?(:compose)
576
484
 
577
- cmd = compose(as_get(opts, from), flags, script: true, args: extra, from: from)
485
+ cmd = compose(as_get(opts, from, 2), flags, script: true, args: extra, from: from)
578
486
  from = :script if from == :run && script?
579
487
  end
580
488
  end
@@ -590,16 +498,16 @@ module Squared
590
498
  graph_deps.flatten(1).sort.each do |proj|
591
499
  next if @@graph[:_].include?(proj)
592
500
 
593
- if (val = ENV["PREREQS_#{proj.instance_variable_get(:@envname)}"] || ENV["PREREQS_#{proj.ref.upcase}"])
501
+ if (val = ENV["PREREQS_#{proj.envname_get}"] || ENV["PREREQS_#{proj.ref.upcase}"])
594
502
  split_escape(val) do |meth|
595
- if proj.respond_to?(meth.to_sym)
503
+ if proj.has?(meth)
596
504
  begin
597
505
  proj.__send__(meth, sync: sync)
598
- rescue StandardError => e
599
- on_error(e, :prereqs, exception: true)
506
+ rescue => e
507
+ on_error(e, exception: true)
600
508
  end
601
509
  else
602
- print_error(name, "method: #{meth}", subject: 'prereqs', hint: 'undefined')
510
+ print_error('prereqs', "method: #{meth}", subject: name, hint: 'undefined')
603
511
  end
604
512
  end
605
513
  elsif proj.build?
@@ -649,24 +557,24 @@ module Squared
649
557
  when Hash
650
558
  begin
651
559
  @clean.each { |cmd, opts| build(cmd.to_s, opts, sync: sync) }
652
- rescue StandardError => e
653
- on_error e, :clean
560
+ rescue => e
561
+ on_error(e, fatal: prod?)
654
562
  end
655
563
  else
656
564
  if @clean.is_a?(Enumerable) && !series?(@clean)
657
565
  @clean.each do |val|
658
- entry = basepath(val = val.to_s)
659
- if entry.directory? && val.match?(%r{[\\/]\z})
660
- log&.warn "rm -rf #{entry}"
661
- rm_rf(entry, verbose: !silent?)
566
+ src = basepath(val = val.to_s)
567
+ if src.directory? && val.match?(%r{[\\/]\z})
568
+ log&.warn "rm -rf #{src}"
569
+ rm_rf(src, verbose: !silent?)
662
570
  else
663
- log&.warn "rm #{entry}"
664
- (val.include?('*') ? Dir[entry] : [entry]).each do |file|
571
+ log&.warn "rm #{src}"
572
+ (val.include?('*') ? Dir[src] : [src]).each do |file|
665
573
  next unless File.file?(file)
666
574
 
667
575
  File.delete(file)
668
- rescue StandardError => e
669
- log&.error e
576
+ rescue => e
577
+ print_error(Logger::ERROR, e, subject: name)
670
578
  end
671
579
  end
672
580
  end
@@ -690,22 +598,20 @@ module Squared
690
598
  end
691
599
  env('GRAPH', suffix: 'PASS') { |val| pass.concat(split_escape(val)) }
692
600
  start, neg = start.partition { |name| !name.start_with?('-') }
693
- data = graph_collect(self, start, pass: neg.map! { |name| name[1..-1] })
601
+ data = graph_collect(self, start, pass: neg.map { |name| name[1..-1] })
694
602
  unless out
695
603
  data[name] << self
696
604
  on :first, :graph
697
605
  end
698
606
  ret = graph_branch(self, data, tasks, out, sync: sync, pass: pass, order: order)
699
- rescue StandardError => e
700
- on_error(e, :graph, exception: true)
607
+ rescue => e
608
+ on_error(e, exception: true, dryrun: !out.nil?)
701
609
  else
702
610
  if out
703
611
  if order
704
- out.map! do |val|
705
- name = ret.find { |proj| val.match?(/ #{Regexp.escape(proj.name)}(?:@\d|\z)/) }&.name
706
- next val unless (n = name && order[name])
707
-
708
- val.subhint(n.succ)
612
+ out.map do |val|
613
+ name = ret.find { |proj| val.match?(/ #{Regexp.escape(proj.name)}(@\d|\z)/) }&.name
614
+ (n = name && order[name]) ? val.subhint(n.succ) : val
709
615
  end
710
616
  else
711
617
  [out, ret]
@@ -866,8 +772,8 @@ module Squared
866
772
  '--home'
867
773
  end
868
774
  cmd << name << version
869
- when :exec
870
- cmd << name unless opts.first.start_with?(/#{name}\b/)
775
+ when :env, :exec
776
+ cmd << name if flag == :env || !opts.first.start_with?(/#{name}\b/)
871
777
  cmd.merge(opts)
872
778
  when :current
873
779
  cmd << '--no-header' unless legacy
@@ -876,7 +782,7 @@ module Squared
876
782
  cmd << name
877
783
  banner = false if flag == :latest || flag == :where
878
784
  end
879
- success?(run(banner: banner, from: :"asdf:#{flag}"), flag == :set || flag == :reshim)
785
+ success?(run(banner: banner, from: symjoin('asdf', flag)), flag == :set || flag == :reshim)
880
786
  end
881
787
 
882
788
  def first(key, *args, **kwargs, &blk)
@@ -893,14 +799,18 @@ module Squared
893
799
 
894
800
  def event(name, key, *args, override: false, **kwargs, &blk)
895
801
  args.unshift(blk) if block_given?
896
- ev = @events[name.to_sym]
802
+ ev = @on[:event][name.to_sym]
897
803
  (override ? ev[key.to_sym] = [] : ev[key.to_sym] ||= []) << [args, kwargs]
898
804
  self
899
805
  end
900
806
 
901
807
  def as(cmd, script, to = nil)
902
808
  data = @as[cmd.to_sym]
903
- (to ? [[script, to]] : script).each { |key, val| data[key.to_s] = val }
809
+ if script.is_a?(Enumerable)
810
+ script.each { |key, val| data[key.to_s] = val }
811
+ elsif to
812
+ data[script.to_s] = to
813
+ end
904
814
  self
905
815
  end
906
816
 
@@ -917,8 +827,8 @@ module Squared
917
827
  self
918
828
  end
919
829
 
920
- def run(cmd = @session, var = nil, exception: self.exception, sync: true, banner: true, from: nil, chdir: path,
921
- interactive: nil, hint: nil, series: false, **)
830
+ def run(cmd = @session, var = nil, exception: exception?, sync: true, banner: true, from: nil, chdir: path,
831
+ interactive: nil, hint: nil, series: false, timeout: nil, **)
922
832
  unless cmd
923
833
  print_error('no command session started', subject: project, hint: from, pass: true)
924
834
  return
@@ -936,6 +846,7 @@ module Squared
936
846
  msg = "#{msg} #{sub_style(h, theme[:active])}" if h
937
847
  exit 1 unless confirm_basic("#{msg}?", cmd, y)
938
848
  end
849
+ timeout = session_timeout cmd if timeout.nil?
939
850
  cmd = session_done cmd
940
851
  log&.info cmd
941
852
  on :first, from
@@ -956,10 +867,13 @@ module Squared
956
867
  end
957
868
  end
958
869
  args = var.is_a?(Hash) ? [var, cmd] : [cmd]
959
- ret = shell(*args, chdir: chdir, exception: exception)
870
+ ret = shell_t(*args, chdir: chdir, exception: exception, timeout: timeout || 0)
960
871
  end
961
- rescue StandardError => e
962
- on_error(e, from, exception: true)
872
+ rescue Timeout::Error => e
873
+ print_error(Logger::ERROR, cmd, subject: name, hint: e)
874
+ exit 1 unless exception?(Logger::DEBUG, Logger::INFO)
875
+ rescue => e
876
+ on_error(e, exception: true)
963
877
  false
964
878
  else
965
879
  on :last, from
@@ -973,6 +887,51 @@ module Squared
973
887
  end
974
888
  end
975
889
 
890
+ def global(*args)
891
+ @global > 0 && @global.anybits?(case args.size
892
+ when 1
893
+ args.first
894
+ when 0
895
+ 1 | 2
896
+ else
897
+ args.reduce(0) { |a, b| a | b }
898
+ end)
899
+ end
900
+
901
+ def global_set(name = nil, **kwargs)
902
+ return if (n = @global) == -1
903
+
904
+ @global = 0
905
+ data = scriptdata(name: name)
906
+ if n.anybits?(1 << 31)
907
+ @global = (n & (1 | 2)) | (1 << 31)
908
+ elsif @output[0] != false
909
+ kwargs.update(@initargs) if name
910
+ if @output[0].nil?
911
+ if data[:script]
912
+ unless kwargs[:script] == false
913
+ script_set(data[:script], args: data.fetch(:args, kwargs[:args]), prod: kwargs[:prod], global: true)
914
+ end
915
+ elsif data[:run]
916
+ run_set(data[:run], global: true)
917
+ end
918
+ if kwargs[:script]
919
+ script_set(kwargs[:script], args: kwargs[:args]) unless data[:env][:script]
920
+ elsif @script
921
+ if @script[:script]
922
+ script_set(@script[:script], args: @script.fetch(:args, kwargs[:args])) unless data[:global][:script]
923
+ elsif @script[:run] && !data[:global][:run]
924
+ run_set @script[:run]
925
+ end
926
+ end
927
+ elsif data[:run] && data[:env][:run]
928
+ run_set(data[:run], global: true)
929
+ end
930
+ end
931
+ self.global = 4 if data[:global][:doc] && doc?
932
+ self.global = 8 if data[:global][:test] && test?
933
+ end
934
+
976
935
  def variable_set(key, *args, **kwargs, &blk)
977
936
  if block_given?
978
937
  if blocks.include?(key)
@@ -988,6 +947,8 @@ module Squared
988
947
  index_set val
989
948
  when :graph
990
949
  graph_set val
950
+ when :timeout
951
+ timeout_set val
991
952
  when :pass
992
953
  pass_set val
993
954
  when :only
@@ -1021,25 +982,26 @@ module Squared
1021
982
  alias apply variable_set
1022
983
 
1023
984
  def enabled?(ref = nil, **)
1024
- return false if ref && !ref?(ref)
985
+ return false if ref && Array(ref).none? { |val| ref?(val) }
1025
986
 
1026
987
  (path.directory? && !path.empty?) || archive?
1027
988
  end
1028
989
 
1029
- def has?(meth, ref = nil)
990
+ def has?(meth, ref = nil, all: false, missing: false)
1030
991
  return false if ref && !ref?(ref)
1031
992
 
1032
- respond_to?(meth = :"#{meth}?") && __send__(meth)
993
+ pred = :"#{meth}?"
994
+ respond_to?(pred, all) ? __send__(pred) : missing
1033
995
  end
1034
996
 
1035
997
  def ref?(val)
1036
998
  @ref.include?(val)
1037
999
  end
1038
1000
 
1039
- def exist?(*args)
1040
- return false if (args = args.compact).empty?
1001
+ def exist?(*args, type: nil)
1002
+ return false if args.first.nil?
1041
1003
 
1042
- basepath(*args).exist?
1004
+ type ? !basepath!(*args, type: type).nil? : basepath(*args).exist?
1043
1005
  end
1044
1006
 
1045
1007
  def build?
@@ -1064,10 +1026,9 @@ module Squared
1064
1026
 
1065
1027
  def prereqs?
1066
1028
  target = self
1067
- loop do
1029
+ begin
1068
1030
  return true if target.graph?
1069
- break unless (target = target.parent)
1070
- end
1031
+ end while (target = target.parent)
1071
1032
  false
1072
1033
  end
1073
1034
 
@@ -1104,7 +1065,7 @@ module Squared
1104
1065
  end
1105
1066
 
1106
1067
  def exclude?(*refs)
1107
- !@exclude.empty? && has_value?(@exclude, refs.flatten)
1068
+ !@exclude.empty? && has_value?(@exclude, *refs)
1108
1069
  end
1109
1070
 
1110
1071
  def task_include?(key, ref = nil)
@@ -1126,7 +1087,7 @@ module Squared
1126
1087
  def log
1127
1088
  return @log unless @log.is_a?(Array)
1128
1089
 
1129
- @log = Logger.new((@log.first if enabled?), **@log.last)
1090
+ @log = Logger.new(enabled? ? @log.first : nil, **@log.last)
1130
1091
  end
1131
1092
 
1132
1093
  def allref(&blk)
@@ -1183,7 +1144,12 @@ module Squared
1183
1144
  def scriptname(from: :run)
1184
1145
  return unless (name = @output[1]) && respond_to?(:compose)
1185
1146
 
1186
- as_get name, from
1147
+ as_get name, from, 2
1148
+ end
1149
+
1150
+ def scriptdata(*keys, name: nil)
1151
+ ret = @workspace.script_find(*@ref, @group, name: name)
1152
+ keys.empty? ? ret : ret.dig(*keys)
1187
1153
  end
1188
1154
 
1189
1155
  def inspect
@@ -1202,21 +1168,123 @@ module Squared
1202
1168
 
1203
1169
  def script_get(*args, key: nil)
1204
1170
  ret = workspace.script_get(*args, group: group, ref: allref)
1205
- return ret unless ret && key
1171
+ ret && key ? ret.fetch(key, nil) : ret
1172
+ end
1173
+
1174
+ def index_get
1175
+ @index
1176
+ end
1177
+
1178
+ def envname_get
1179
+ @envname
1180
+ end
1206
1181
 
1207
- ret.fetch(key, nil)
1182
+ def graph_get
1183
+ @graph
1208
1184
  end
1209
1185
 
1210
1186
  private
1211
1187
 
1188
+ def initialize_ref(ref)
1189
+ @ref << ref unless @exclude.include?(ref)
1190
+ end
1191
+
1192
+ def initialize_build(ref, **kwargs)
1193
+ initialize_ref ref
1194
+ if (@script = @workspace.script_get(group: @group, ref: ref))
1195
+ if @script[:log] && !kwargs.key?(:log)
1196
+ kwargs[:log] = @script[:log]
1197
+ @log = nil
1198
+ end
1199
+ @depend = @script[:depend] if @depend.nil?
1200
+ @doc = @script[:doc] if @doc.nil?
1201
+ @lint = @script[:lint] if @lint.nil?
1202
+ @test = @script[:test] if @test.nil?
1203
+ @clean = @script[:clean] if @clean.nil?
1204
+ @exclude = @script[:exclude] if @exclude.empty? && @script.key?(:exclude)
1205
+ end
1206
+ @workspace.events_get(group: @group, ref: ref)&.each do |task, data|
1207
+ data.each { |ev, blk| @on[:event][ev][task] ||= [blk] }
1208
+ end
1209
+ initialize_logger kwargs[:log]
1210
+ global_set(script: kwargs[:script], args: kwargs[:args], prod: kwargs[:prod])
1211
+ end
1212
+
1213
+ def initialize_logger(log)
1214
+ return if @log
1215
+
1216
+ log = log.is_a?(Hash) ? log.dup : { file: log }
1217
+ file = if (val = env('LOG_FILE'))
1218
+ Time.now.strftime(val)
1219
+ elsif (val = env('LOG_AUTO'))
1220
+ require 'date'
1221
+ "#{@name}-%s.log" % [case val
1222
+ when 'y', 'year'
1223
+ Date.today.year
1224
+ when 'm', 'month'
1225
+ Date.today.strftime('%Y-%m')
1226
+ when 'd', 'day', '1'
1227
+ Date.today
1228
+ else
1229
+ val.include?('%') ? Time.now.strftime(val) : Time.now.strftime('%FT%T%:z')
1230
+ end]
1231
+ elsif (val = log[:file])
1232
+ if val.is_a?(String)
1233
+ Time.now.strftime(val)
1234
+ else
1235
+ require 'date'
1236
+ "#{@name}-#{Date.today}.log"
1237
+ end
1238
+ end
1239
+ .yield_self do |path|
1240
+ next unless path
1241
+
1242
+ @workspace.home.join(env('LOG_DIR', ''), path).realdirpath
1243
+ rescue => e
1244
+ print_error e
1245
+ end
1246
+ log[:progname] ||= @name
1247
+ env('LOG_LEVEL', ignore: false) { |val| log[:level] = val.start_with?(/\d/) ? log_sym(val.to_i) : val }
1248
+ log.delete(:file)
1249
+ @log = [file, log]
1250
+ end
1251
+
1252
+ def initialize_env(dev: nil, prod: nil, **)
1253
+ @dev = env_match('BUILD', dev, suffix: 'DEV', strict: true)
1254
+ @prod = env_match('BUILD', prod, suffix: 'PROD', strict: true)
1255
+ unless @output[0] == false
1256
+ env('BUILD', suffix: 'ENV') do |val|
1257
+ next unless (val = parse_json(val, kind: nil)).is_a?(Hash)
1258
+
1259
+ @output[2] = val
1260
+ end
1261
+ end
1262
+ unless @output[0] == false || @output[0].is_a?(Array)
1263
+ env('BUILD', suffix: 'OPTS') do |val|
1264
+ n = @output[0] ? 1 : 3
1265
+ @output[n] = merge_opts @output[n], shell_split(val)
1266
+ end
1267
+ env(ref.to_s.upcase, suffix: 'OPTS') { |val| @output[4] = merge_opts @output[4], shell_split(val) }
1268
+ end
1269
+ env('BUILD', suffix: 'VERSION') { |val| self.version = val }
1270
+ env('BUILD', strict: true) do |val|
1271
+ if val == '0'
1272
+ @output = [false]
1273
+ elsif script?
1274
+ script_set(val, global: 1 << 31)
1275
+ else
1276
+ run_set(val, global: 1 << 31)
1277
+ end
1278
+ end
1279
+ end
1280
+
1212
1281
  def puts(*args, **kwargs)
1213
1282
  log_console(*args, pipe: kwargs[:pipe] || pipe)
1214
1283
  end
1215
1284
 
1216
1285
  def run_s(*cmd, sync: true, banner: !silent?, from: nil, **kwargs)
1217
- cmd.flatten!
1218
1286
  case cmd.last
1219
- when Hash, TrueClass, FalseClass
1287
+ when Hash, true, false
1220
1288
  var = cmd.pop
1221
1289
  end
1222
1290
  on :first, from
@@ -1225,8 +1293,8 @@ module Squared
1225
1293
  print_run val, banner
1226
1294
  run(val, var, sync: sync, banner: banner, **kwargs)
1227
1295
  end
1228
- rescue StandardError => e
1229
- on_error(e, from, exception: kwargs.fetch(:exception, exception))
1296
+ rescue => e
1297
+ on_error(e, exception: kwargs.fetch(:exception, exception?))
1230
1298
  end
1231
1299
  on :last, from
1232
1300
  end
@@ -1240,11 +1308,8 @@ module Squared
1240
1308
  when Proc
1241
1309
  instance_eval(&obj)
1242
1310
  when Method
1243
- args = if (n = obj.arity.abs) > 0
1244
- Array.new(n).tap { |data| data[0] = self }
1245
- else
1246
- []
1247
- end
1311
+ n = obj.arity.abs
1312
+ args = n > 0 ? Array.new(n).tap { |data| data[0] = self } : []
1248
1313
  obj.call(*args)
1249
1314
  else
1250
1315
  if series?(obj)
@@ -1257,8 +1322,25 @@ module Squared
1257
1322
  end
1258
1323
  end
1259
1324
 
1260
- def graph_branch(target, data, tasks = nil, out = nil, sync: true, pass: [], done: [], order: nil, depth: 0,
1261
- single: false, last: false, context: nil)
1325
+ def run_e(cmd, stderr: false, banner: true, **kwargs)
1326
+ require 'open3'
1327
+ if stderr
1328
+ Open3.popen3(cmd) do |_, out, err|
1329
+ n = write_lines(out, banner: banner, pass: true, **kwargs)
1330
+ if n == 0
1331
+ n = write_lines(err, banner: banner)
1332
+ success?(n == 0, n == 0 && !banner.nil?)
1333
+ else
1334
+ write_lines(err, loglevel: Logger::DEBUG)
1335
+ end
1336
+ end
1337
+ else
1338
+ Open3.popen2e(cmd) { |_, out| write_lines(out, banner: banner, **kwargs) }
1339
+ end
1340
+ end
1341
+
1342
+ def graph_branch(target, data, tasks = nil, out = nil, sync: true, pass: [], done: [], depth: 0, single: false,
1343
+ last: false, order: nil, **kwargs)
1262
1344
  tag = ->(proj) { "#{proj.name}#{"@#{proj.version}" if SEM_VER.match?(proj.version)}" }
1263
1345
  uniq = lambda do |name|
1264
1346
  return [] unless (ret = data[name])
@@ -1294,6 +1376,7 @@ module Squared
1294
1376
  "#{single ? ' ' : a}#{' ' * depth.pred}#{last ? d : c}#{b * 3}#{items.empty? ? b : e} #{f}"
1295
1377
  end
1296
1378
  end
1379
+ ctx = { sync: sync, pass: pass, done: done, order: order, single: single, depth: depth.succ, context: target }
1297
1380
  items.each_with_index do |proj, i|
1298
1381
  next if done.include?(proj)
1299
1382
 
@@ -1306,8 +1389,7 @@ module Squared
1306
1389
  end
1307
1390
  end
1308
1391
  unless target.name == name || (none = (t - done).empty?)
1309
- graph_branch(proj, data, tasks, out, sync: sync, pass: pass, done: done, order: order, depth: depth.succ,
1310
- single: single, last: j == true, context: target)
1392
+ graph_branch(proj, data, tasks, out, last: j == true, **ctx)
1311
1393
  end
1312
1394
  if out
1313
1395
  if none
@@ -1320,7 +1402,7 @@ module Squared
1320
1402
  final = data.keys.last
1321
1403
  while k < depth
1322
1404
  indent = k > 0 ? ((last && !j) || (j && k == depth.pred) || single) : j && last && depth == 1
1323
- s += "#{indent || (last && data[final].last == context) ? ' ' : a} "
1405
+ s += "#{indent || (last && data[final].last == kwargs[:context]) ? ' ' : a} "
1324
1406
  k += 1
1325
1407
  end
1326
1408
  s += "#{j ? d : c}#{b * 3} #{tag.call(proj)}"
@@ -1346,14 +1428,14 @@ module Squared
1346
1428
  next if pass.include?(meth)
1347
1429
 
1348
1430
  if workspace.task_defined?(cmd = task_join(name, meth))
1349
- if ENV.key?(key = "BANNER_#{name.upcase}")
1431
+ if ENV.key?(key = "BANNER_#{envname_get}")
1350
1432
  key = nil
1351
1433
  else
1352
1434
  ENV[key] = '0'
1353
1435
  end
1354
1436
  run(cmd, sync: false, banner: false)
1355
1437
  ENV.delete(key) if key
1356
- elsif proj.has?(meth, (workspace.baseref unless tasks || graph))
1438
+ elsif proj.has?(meth, tasks || graph ? nil : workspace.baseref)
1357
1439
  proj.__send__(meth.to_sym, sync: sync)
1358
1440
  end
1359
1441
  end
@@ -1364,8 +1446,7 @@ module Squared
1364
1446
  end
1365
1447
 
1366
1448
  def graph_collect(target, start = [], data: {}, pass: [], root: [])
1367
- deps = []
1368
- (start.empty? ? target.instance_variable_get(:@graph) : start)&.each do |val|
1449
+ deps = Array(start.empty? ? target.graph_get : start).each_with_object([]) do |val, out|
1369
1450
  next if pass.include?(val)
1370
1451
 
1371
1452
  if (obj = workspace.find(name: val))
@@ -1380,8 +1461,8 @@ module Squared
1380
1461
  end
1381
1462
  next if (objs = data.fetch(name, [])).include?(target)
1382
1463
 
1383
- deps << proj
1384
- deps.concat(objs)
1464
+ out << proj
1465
+ out.concat(objs)
1385
1466
  end
1386
1467
  end
1387
1468
  deps.uniq!
@@ -1392,22 +1473,22 @@ module Squared
1392
1473
 
1393
1474
  def graph_deps(target = self)
1394
1475
  key = target.name
1395
- return @@graph[key] if @@graph.key?(key)
1396
-
1397
- base = []
1398
- deps = []
1399
- loop do
1400
- deps.concat(graph_branch(target, graph_collect(target), []))
1401
- break unless (target = target.parent)
1476
+ @@graph[key] ||= begin
1477
+ base = []
1478
+ deps = []
1479
+ loop do
1480
+ deps.concat(graph_branch(target, graph_collect(target), []))
1481
+ break unless (target = target.parent)
1402
1482
 
1403
- base << target
1483
+ base << target
1484
+ end
1485
+ deps.uniq!
1486
+ [base, deps]
1404
1487
  end
1405
- deps.uniq!
1406
- @@graph[key] = [base, deps]
1407
1488
  end
1408
1489
 
1409
- def env(key, default = nil, suffix: nil, equals: nil, ignore: nil, strict: false)
1410
- name = "#{key}_#{@envname}"
1490
+ def env(key, default = nil, suffix: nil, strict: false, ignore: nil, **kwargs, &blk)
1491
+ name = "#{key}_#{envname_get}"
1411
1492
  ret = if suffix
1412
1493
  ENV.fetch("#{name}_#{suffix}", '')
1413
1494
  elsif strict
@@ -1416,15 +1497,7 @@ module Squared
1416
1497
  ignore = ['0'].freeze if ignore.nil?
1417
1498
  ENV[name] || ENV.fetch(key, '')
1418
1499
  end
1419
- if equals.nil?
1420
- ret = default if ret.empty? || (ignore && Array(ignore).any? { |val| ret == val.to_s })
1421
- return ret if ret.nil?
1422
- else
1423
- ret = Array(equals).any? { |val| ret == val.to_s }
1424
- end
1425
- return yield ret if block_given?
1426
-
1427
- ret
1500
+ env_yield(ret, default, ignore: ignore, **kwargs, &blk)
1428
1501
  end
1429
1502
 
1430
1503
  def session(*cmd, prefix: cmd.first, main: true, path: true, options: true)
@@ -1432,7 +1505,7 @@ module Squared
1432
1505
  if path && (val = shell_bin(prefix))
1433
1506
  cmd[0] = shell_quote(val, force: false)
1434
1507
  end
1435
- ret = JoinSet.new(cmd.flatten(1))
1508
+ ret = JoinSet.new(cmd)
1436
1509
  if options
1437
1510
  env("#{prefix.upcase}_OPTIONS") do |val|
1438
1511
  if val.start_with?('-')
@@ -1442,9 +1515,17 @@ module Squared
1442
1515
  end
1443
1516
  end
1444
1517
  end
1445
- return ret unless main
1518
+ cache = @timeout[:_]
1519
+ cache[1] ||= workspace.timeout_get(*@ref, group: group)
1520
+ cache[0][ret] = if cmd[1..-1].find { |val| val =~ /\A([A-Za-z][\w-]*)(?<![-_])(?: |\z)/ }
1521
+ command = :"#{prefix}_#{$1}"
1522
+ cache[1][command] || @timeout[command]
1523
+ end || cache[1][prefix.to_sym] || @timeout[prefix.to_sym]
1524
+ main ? @session = ret : ret
1525
+ end
1446
1526
 
1447
- @session = ret
1527
+ def session_timeout(cmd)
1528
+ @timeout[:_][0][cmd] || @timeout[:_][2]
1448
1529
  end
1449
1530
 
1450
1531
  def session_output(*cmd, **kwargs)
@@ -1468,18 +1549,18 @@ module Squared
1468
1549
  end
1469
1550
 
1470
1551
  def session_apply(val, args: nil, kwargs: nil, pass: [], keys: [:opts], exclude: [])
1471
- a = []
1472
1552
  b = {}
1473
- Array(val).each do |c|
1553
+ a = Array(val).each_with_object([]) do |c, out|
1474
1554
  d, e, f = session_get c
1475
- unless (f -= exclude).empty?
1555
+ f -= exclude
1556
+ unless f.empty?
1476
1557
  h = []
1477
1558
  i = {}
1478
- session_apply(f.map!(&:to_s), args: h, kwargs: i, pass: pass, keys: keys, exclude: exclude.concat(f))
1479
- a.concat(h)
1559
+ session_apply(f.map(&:to_s), args: h, kwargs: i, pass: pass, keys: keys, exclude: exclude.concat(f))
1560
+ out.concat(h)
1480
1561
  append_keys(b, i, *keys)
1481
1562
  end
1482
- a.concat(d)
1563
+ out.concat(d)
1483
1564
  append_keys(b, e, *keys)
1484
1565
  end
1485
1566
  if args
@@ -1512,23 +1593,48 @@ module Squared
1512
1593
  OptionPartition.arg?(target, *args, **kwargs)
1513
1594
  end
1514
1595
 
1515
- def option(*args, target: @session, prefix: target&.first, **kwargs)
1596
+ def option(*args, target: @session, prefix: target&.first, path: false, escape: false, quote: false, pat: nil,
1597
+ **kwargs)
1516
1598
  return unless prefix
1517
1599
 
1518
- args.each do |val|
1519
- next unless (ret = env(env_key(prefix.to_s.stripext, val), **kwargs))
1600
+ args.each do |key|
1601
+ next unless (ret = env(env_key(prefix.to_s.stripext, key), **kwargs)) && (!pat || ret.match?(pat))
1520
1602
 
1603
+ if path
1604
+ target << quote_option(key, basepath(ret))
1605
+ elsif quote
1606
+ target << quote_option(key, ret)
1607
+ elsif escape
1608
+ target << shell_option(key, ret)
1609
+ end
1521
1610
  return block_given? ? yield(ret) : ret
1522
1611
  end
1523
1612
  nil
1524
1613
  end
1525
1614
 
1526
- def option_clear(opts, empty = true, target: @session, **kwargs)
1527
- return unless target
1615
+ def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1616
+ grep = grep.empty? ? nil : matchmap(grep, prefix)
1617
+ sub = stdin? ? nil : as_a(sub)
1618
+ ret = 0
1619
+ lines = data.each_with_object([]) do |line, out|
1620
+ next if grep&.none? { |pat| pat.match?(line) }
1621
+ next if block_given? && !(line = yield(line, ret))
1528
1622
 
1529
- OptionPartition.clear(target, opts, styles: theme[:inline], **kwargs)
1530
- opts.clear if empty
1531
- nil
1623
+ if loglevel
1624
+ log&.add loglevel, line
1625
+ else
1626
+ sub&.each { |h| sub_style!(line, **h) }
1627
+ if banner
1628
+ out << line
1629
+ else
1630
+ puts line
1631
+ end
1632
+ end
1633
+ ret += 1
1634
+ break out if first
1635
+ end
1636
+ print_item banner, lines if banner && (ret > 0 || (!pass && !first))
1637
+ ret
1532
1638
  end
1533
1639
 
1534
1640
  def print_success
@@ -1538,7 +1644,18 @@ module Squared
1538
1644
  def print_error(*args, loglevel: Logger::WARN, **kwargs)
1539
1645
  return unless warning?
1540
1646
 
1541
- warn log_message(loglevel, *args, **kwargs)
1647
+ loglevel = args.shift if args.first.is_a?(Numeric) && args.first.between?(Logger::DEBUG, Logger::UNKNOWN)
1648
+ if loglevel >= Logger::WARN && exception?(Logger::ERROR, Logger::FATAL)
1649
+ $stderr.puts log_message([loglevel, Logger::Error].max, *args, **kwargs)
1650
+ if exception == Logger::ERROR
1651
+ ex = [args.first, kwargs[:subject], kwargs[:hint]].compact
1652
+ raise(ex.find { |val| val.class < Exception } || ex.first)
1653
+ else
1654
+ exit 1
1655
+ end
1656
+ else
1657
+ warn log_message(loglevel, *args, **kwargs)
1658
+ end
1542
1659
  end
1543
1660
 
1544
1661
  def print_run(cmd, banner = true, verbose: nil, **)
@@ -1554,19 +1671,19 @@ module Squared
1554
1671
  puts val unless val.empty? || (val.size == 1 && !val.first)
1555
1672
  end
1556
1673
 
1557
- def print_banner(*lines, client: false, styles: theme[:banner], border: borderstyle, **)
1674
+ def print_banner(*lines, client: false, styles: theme[:banner], border: borderstyle)
1558
1675
  pad = 0
1559
1676
  if styles
1560
1677
  if styles.any? { |s| s.to_s.end_with?('!') }
1561
1678
  pad = 1
1562
1679
  elsif !client && styles.size <= 1
1563
- styles = [:bold] + styles
1680
+ styles += [:bold]
1564
1681
  end
1565
1682
  end
1566
1683
  n = line_width lines
1567
1684
  ch = ' ' * pad
1568
1685
  index = -1
1569
- lines.map! do |val|
1686
+ ret = lines.map do |val|
1570
1687
  index += 1
1571
1688
  val = ch + val.ljust(n - (pad * 2)) + ch
1572
1689
  if styles && (pad == 1 || index == 0)
@@ -1575,23 +1692,24 @@ module Squared
1575
1692
  val
1576
1693
  end
1577
1694
  end
1578
- (lines << sub_style(ARG[:BORDER][1] * n, border)).join("\n")
1695
+ ret << sub_style(ARG[:BORDER][1] * n, border)
1696
+ ret.join("\n")
1579
1697
  end
1580
1698
 
1581
- def print_footer(*lines, sub: nil, reverse: false, right: false, border: borderstyle, **)
1699
+ def print_footer(*lines, sub: nil, reverse: false, right: false, border: borderstyle)
1582
1700
  n = line_width lines
1583
1701
  sub = as_a sub
1584
- lines.map! do |val|
1702
+ ret = lines.map do |val|
1585
1703
  s = right ? val.rjust(n) : val.ljust(n)
1586
1704
  sub.each { |h| sub_style!(s, **h) }
1587
1705
  s
1588
1706
  end
1589
- [sub_style(ARG[:BORDER][1] * n, border)].concat(lines)
1590
- .tap { |ret| ret.reverse! if reverse }
1591
- .join("\n")
1707
+ ret.unshift(sub_style(ARG[:BORDER][1] * n, border))
1708
+ ret.reverse! if reverse
1709
+ ret.join("\n")
1592
1710
  end
1593
1711
 
1594
- def print_status(*args, from: nil, **kwargs)
1712
+ def print_status(*args, from: :completed, **kwargs)
1595
1713
  return if stdin? || silent?
1596
1714
 
1597
1715
  case from
@@ -1617,7 +1735,7 @@ module Squared
1617
1735
  workspace.format_desc([@desc, action, flag].compact, opts, **kwargs)
1618
1736
  end
1619
1737
 
1620
- def format_banner(cmd, banner: true, hint: nil, strip: nil)
1738
+ def format_banner(cmd, banner: true, hint: nil, strip: nil, quote: false)
1621
1739
  return unless banner && banner?
1622
1740
 
1623
1741
  if (data = workspace.banner_get(*@ref, group: group))
@@ -1625,7 +1743,7 @@ module Squared
1625
1743
 
1626
1744
  client = true
1627
1745
  else
1628
- data = Struct::BannerData.new(true, [:path], theme[:banner], theme[:border])
1746
+ data = Struct::BannerData.new(true, theme[:banner], theme[:border], [:path])
1629
1747
  end
1630
1748
  if verbose
1631
1749
  out = []
@@ -1638,6 +1756,7 @@ module Squared
1638
1756
  cmd = cmd.gsub(/(?:#{s = Regexp.escape(File.join(path, ''))}?(?=["'])|#{s})/, '')
1639
1757
  .gsub(/(?: -[^ ])? (?:""|'')/, '')
1640
1758
  end
1759
+ cmd.gsub!(/(?<= )(["'])([\w.-]+)\1(?= |\z)/, '\2') unless quote
1641
1760
  out << cmd.subhint(hint)
1642
1761
  end
1643
1762
  data.order.each do |val|
@@ -1672,7 +1791,7 @@ module Squared
1672
1791
  unless items.empty?
1673
1792
  pad = items.size.to_s.size
1674
1793
  items.each_with_index do |val, i|
1675
- next unless matchany?(val.first, reg)
1794
+ next unless matchany?(reg, val.first)
1676
1795
 
1677
1796
  out << ('%*d. %s' % [pad, i.succ, block_given? ? yield(val) : val.first])
1678
1797
  end
@@ -1686,7 +1805,7 @@ module Squared
1686
1805
  out << ''
1687
1806
  end
1688
1807
  if from
1689
- out << (from = from.to_s)
1808
+ out << from
1690
1809
  /\A(#{Regexp.escape(from)})(.*)\z/
1691
1810
  end
1692
1811
  else
@@ -1719,7 +1838,8 @@ module Squared
1719
1838
  end
1720
1839
  end
1721
1840
  data.each do |key, val|
1722
- next if (key = key.to_s).start_with?('__')
1841
+ key = key.to_s
1842
+ next if key.start_with?('__')
1723
1843
 
1724
1844
  if val.nil? || extra || session_arg?(key, target: target)
1725
1845
  OptionPartition.delete_key(target, key)
@@ -1732,7 +1852,7 @@ module Squared
1732
1852
  append_repeat(key, val, target: target)
1733
1853
  when Numeric
1734
1854
  target << basic_option(key, val)
1735
- when FalseClass
1855
+ when false
1736
1856
  target << shell_option(key).sub(/^--(?!no-)/, '--no-')
1737
1857
  when Pathname
1738
1858
  target << shell_option(key, val, escape: false)
@@ -1769,9 +1889,7 @@ module Squared
1769
1889
 
1770
1890
  def append_first(*list, target: @session, flag: true, equals: false, escape: true, quote: true, force: true,
1771
1891
  **kwargs)
1772
- return if list.empty?
1773
-
1774
- list.flatten.each do |opt|
1892
+ list.each do |opt|
1775
1893
  next unless (val = option(opt, **kwargs))
1776
1894
 
1777
1895
  return target << if flag
@@ -1785,30 +1903,25 @@ module Squared
1785
1903
 
1786
1904
  def append_option(*list, target: @session, no: false, equals: false, escape: true, quote: true, force: true,
1787
1905
  **kwargs)
1788
- return if list.empty?
1789
-
1790
1906
  kwargs[:ignore] = false if no && !kwargs.key?(:ignore)
1791
- ret = []
1792
- list.flatten.each do |flag|
1907
+ ret = list.each_with_object([]) do |flag, out|
1793
1908
  next unless (val = option(flag, target: target, **kwargs))
1794
1909
 
1795
1910
  if no && val == '0'
1796
1911
  flag = "no-#{flag}"
1797
1912
  val = nil
1798
1913
  end
1799
- ret << shell_option(flag, (val if equals), escape: escape, quote: quote, force: force)
1914
+ out << shell_option(flag, (val if equals), escape: escape, quote: quote, force: force)
1800
1915
  end
1801
1916
  merge_list target, ret unless ret.empty?
1802
- ret
1803
1917
  end
1804
1918
 
1805
- def append_nocolor(target: @session)
1806
- target << '--no-color' if !ARG[:COLOR] || stdin? || option('color', target: target, equals: '0')
1919
+ def append_nocolor(flag = '--no-color', target: @session, **kwargs)
1920
+ target << flag if !ARG[:COLOR] || stdin? || option('color', target: target, equals: '0', **kwargs)
1807
1921
  end
1808
1922
 
1809
1923
  def append_keys(base, data, *keys)
1810
- out = {}
1811
- keys.each do |key|
1924
+ ret = keys.each_with_object({}) do |key, out|
1812
1925
  next unless data.key?(key)
1813
1926
 
1814
1927
  out[key] = case (val = data[key])
@@ -1821,7 +1934,7 @@ module Squared
1821
1934
  end
1822
1935
  end
1823
1936
  base.update(data)
1824
- .update(out)
1937
+ .update(ret)
1825
1938
  end
1826
1939
 
1827
1940
  def merge_opts(base, data)
@@ -1864,22 +1977,9 @@ module Squared
1864
1977
  end
1865
1978
  end
1866
1979
 
1867
- def merge_list(base, data)
1868
- data = Array(data)
1869
- case base
1870
- when Array
1871
- base.concat(data)
1872
- when Set
1873
- base.merge(data)
1874
- else
1875
- Array(base).concat(data)
1876
- end
1877
- end
1878
-
1879
1980
  def collect_hash(data, pass: [])
1880
- ret = []
1881
- data.each { |key, val| ret.concat(val) unless pass.include?(key) }
1882
- ret
1981
+ data = data.reject { |key,| pass.include?(key) } unless pass.empty?
1982
+ data.values.flatten
1883
1983
  end
1884
1984
 
1885
1985
  def replace_bin(val)
@@ -1893,14 +1993,13 @@ module Squared
1893
1993
  ret = JSON.parse(val)
1894
1994
  raise_error 'invalid JSON'.subhint(kind.name), val, hint: hint if kind && !ret.is_a?(kind)
1895
1995
  ret
1896
- rescue StandardError => e
1897
- log&.warn e
1898
- print_error(e, subject: name)
1996
+ rescue => e
1997
+ print_error(kind ? Logger::ERROR : Logger::INFO, e, subject: name)
1899
1998
  end
1900
1999
 
1901
2000
  def parse_env(key)
1902
2001
  env(key) do |val|
1903
- val.start_with?('-') ? shell_parse(val) : split_escape(val).map! { |opt| fill_option(opt) }
2002
+ val.start_with?('-') ? shell_parse(val) : split_escape(val).map { |opt| fill_option(opt) }
1904
2003
  end || []
1905
2004
  end
1906
2005
 
@@ -1922,65 +2021,46 @@ module Squared
1922
2021
  confirm("#{msg} [#{sub_style(target.to_s, style.is_a?(Symbol) ? theme[style] : style)}]", default, **kwargs)
1923
2022
  end
1924
2023
 
1925
- def confirm_outdated(pkg, ver, type, cur = nil, lock: false, col0: 0, col1: 0, col2: nil, col3: 0, col4: 0,
1926
- **kwargs)
1927
- h = sub_style(semrev(type).upcase, (type == 1 && theme[:major]) || theme[:header])
1928
- case col0
1929
- when 0
1930
- col0 = "#{h}: "
1931
- when Numeric
1932
- puts h
1933
- col0 = ' ' * col0
1934
- else
1935
- puts h
1936
- end
1937
- b = sub_style pkg.ljust(col1), theme[:inline]
1938
- cur ||= 'locked' if lock
1939
- c = if cur
1940
- cur = cur.ljust(col2 || cur.size.succ)
1941
- lock ? sub_style(cur, color(:red)) : cur
1942
- end
1943
- d = type == 1 || lock ? 'N' : 'Y'
1944
- e = "#{col0}#{b}#{c}#{sub_style(col1 > 0 ? ver.ljust(col3) : ver.rjust(ver.size.succ), theme[:inline])}"
1945
- confirm("#{e}#{col4 > 0 ? ' ' * [col4 - e.stripstyle.size - 1, 2].max : ' '}", d, **kwargs)
1946
- end
1947
-
1948
2024
  def confirm_semver(msg, type, style: (type == 1 && theme[:major]) || :inline, timeout: 0, **kwargs)
1949
- confirm_basic(msg, semrev(type), type == 1 ? 'N' : 'Y', style: style, timeout: timeout, **kwargs)
2025
+ rev = case type
2026
+ when 1
2027
+ 'major'
2028
+ when 2
2029
+ 'minor'
2030
+ else
2031
+ 'patch'
2032
+ end
2033
+ confirm_basic(msg, rev, type == 1 ? 'N' : 'Y', style: style, timeout: timeout, **kwargs)
1950
2034
  end
1951
2035
 
1952
2036
  def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil, multiple: false,
1953
2037
  force: true, **kwargs)
1954
2038
  puts unless series || printfirst?
1955
- ret = choice(msg, list, multiple: multiple, force: force, **kwargs).tap do |val|
1956
- next unless !val || val.empty?
1957
-
2039
+ ret = choice(msg, list, multiple: multiple, force: force, **kwargs)
2040
+ unless ret && !ret.empty?
1958
2041
  exit 1 if force
1959
- return nil
2042
+ return
1960
2043
  end
1961
- ret = multiple ? ret.map! { |val| val.sub(trim, '') } : ret.sub(trim, '') if trim
2044
+ ret = multiple ? ret.map { |val| val.sub(trim, '') } : ret.sub(trim, '') if trim
1962
2045
  if column
1963
2046
  a, b = Array(column)
1964
- ret = Array(ret).map! { |val| val[a, b || 1] }
2047
+ ret = Array(ret).map { |val| val[a, b || 1] }
1965
2048
  ret = ret.first unless multiple
1966
2049
  end
1967
2050
  if accept
1968
2051
  hint = Array(ret).map { |val| sub_style(val.to_s, theme[:inline]) }.join(', ')
1969
2052
  accept = Array(accept).map { |val| Array(val) }
1970
2053
  ret = Array(ret) if accept.any? { |val| val[1] == true }
1971
- loop do
1972
- item = accept.first
2054
+ begin
2055
+ item = accept.shift
1973
2056
  c = confirm("#{item[0]}#{" [#{hint}]" if hint}", item[2] ? 'Y' : 'N')
1974
2057
  if item[1] == true
1975
2058
  ret << c
1976
2059
  elsif !c
1977
- break
2060
+ exit 1
1978
2061
  end
1979
2062
  hint = nil
1980
- accept.shift
1981
- break if accept.empty?
1982
- end
1983
- exit 1 unless accept.empty?
2063
+ end until accept.empty?
1984
2064
  end
1985
2065
  if values
1986
2066
  ret = Array(ret)
@@ -1991,7 +2071,7 @@ module Squared
1991
2071
  force = false
1992
2072
  end
1993
2073
  val = readline(val, force: force)
1994
- ret << (val unless val.empty?)
2074
+ ret << (val.empty? ? nil : val)
1995
2075
  end
1996
2076
  end
1997
2077
  printsucc unless series
@@ -2009,10 +2089,11 @@ module Squared
2009
2089
  def command_args(args, min: 0, force: false, **kwargs)
2010
2090
  return if args.size > min || option('i', 'interactive', **kwargs, equals: '0')
2011
2091
 
2012
- readline('Enter arguments', force: force)
2092
+ ret = readline('Enter arguments', force: force)
2093
+ args << ret unless ret.empty?
2013
2094
  end
2014
2095
 
2015
- def block_args(fallback = nil, &blk)
2096
+ def block_args(fallback = [], &blk)
2016
2097
  return fallback if (ret = instance_eval(&blk)).nil?
2017
2098
 
2018
2099
  Array(ret)
@@ -2031,33 +2112,40 @@ module Squared
2031
2112
  if workspace.powershell?
2032
2113
  "#{shell_bin('powershell.exe')} -Command \"& {#{args.join(' ; ')}}\"#{out}"
2033
2114
  else
2034
- args.map! { |val| "#{val}#{out}" }.join(' && ')
2115
+ args.map { |val| "#{val}#{out}" }.join(' && ')
2035
2116
  end
2036
2117
  end
2037
2118
 
2038
2119
  def relativepath(*list, all: false)
2039
- list.flatten.map! { |val| Pathname.new(val) }.select { |val| projectpath?(val) }.map! do |val|
2040
- ret = (val.absolute? ? val.relative_path_from(path) : val.cleanpath).to_s
2041
- all && val.to_s.end_with?('/') ? "#{ret}/*" : ret
2042
- end
2120
+ list.map { |val| Pathname.new(val) }
2121
+ .select { |val| projectpath?(val) }
2122
+ .map do |val|
2123
+ ret = (val.absolute? ? val.relative_path_from(path) : val.cleanpath).to_s
2124
+ all && val.to_s.end_with?('/') ? "#{ret}/*" : ret
2125
+ end
2043
2126
  end
2044
2127
 
2045
- def projectmap(files, parent: false, pass: true)
2128
+ def projectmap(files, resolve: true, parent: false, pass: true)
2046
2129
  unless parent
2047
- proj = files.select { |val| projectpath?(val) }
2048
- raise_error 'pathspec not within worktree' unless pass || files.size == proj.size
2049
- files = proj
2130
+ files = files.select { |val| projectpath?(val) }.tap do |list|
2131
+ next if pass || files.size == list.size
2132
+
2133
+ raise_error 'pathspec not within worktree'
2134
+ end
2135
+ end
2136
+ files.map do |val|
2137
+ if val == '.'
2138
+ '.'
2139
+ else
2140
+ val = basepath(val)
2141
+ val = val.relative_path_from(path) unless resolve
2142
+ shell_quote val
2143
+ end
2050
2144
  end
2051
- files.map { |val| val == '.' ? '.' : shell_quote(basepath(val)) }
2052
2145
  end
2053
2146
 
2054
- def matchmap(list, prefix = nil)
2055
- list.map do |val|
2056
- next val if val.is_a?(Regexp)
2057
-
2058
- val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
2059
- Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
2060
- end
2147
+ def symjoin(*args, char: ':')
2148
+ args.join(char).to_sym
2061
2149
  end
2062
2150
 
2063
2151
  def semver(val)
@@ -2082,10 +2170,10 @@ module Squared
2082
2170
  return -1 if (b = other.scan(SEM_VER)).empty?
2083
2171
  return 1 if (a = val.scan(SEM_VER)).empty?
2084
2172
 
2085
- a, b = [a.first, b.first].map! do |c|
2173
+ a, b = [a.first, b.first].map do |c|
2086
2174
  d = begin
2087
2175
  Integer(c[5]).to_s
2088
- rescue StandardError
2176
+ rescue
2089
2177
  c[5] ? '-1' : '0'
2090
2178
  end
2091
2179
  [c[0], c[2], c[4] || '0', d]
@@ -2123,24 +2211,13 @@ module Squared
2123
2211
  end
2124
2212
 
2125
2213
  def semtype(cur, lat)
2126
- if semmajor?(cur, lat)
2127
- 1
2128
- else
2129
- cur[2] == lat[2] ? 3 : 2
2130
- end
2214
+ return 1 if semmajor?(cur, lat)
2215
+
2216
+ cur[2] == lat[2] ? 3 : 2
2131
2217
  end
2132
2218
 
2133
- def semrev(type)
2134
- case type
2135
- when 1
2136
- 'major'
2137
- when 2
2138
- 'minor'
2139
- when 3
2140
- 'patch'
2141
- else
2142
- 'unknown'
2143
- end
2219
+ def semmajor?(cur, want)
2220
+ (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
2144
2221
  end
2145
2222
 
2146
2223
  def semgte?(val, other = nil)
@@ -2166,7 +2243,7 @@ module Squared
2166
2243
  def shortname(*args, suffix: '?', delim: ',', pass: false)
2167
2244
  return unless TASK_METADATA || pass
2168
2245
 
2169
- args.map! do |ch|
2246
+ args.map do |ch|
2170
2247
  "#{ch}/#{case ch
2171
2248
  when 'i'
2172
2249
  'nteractive'
@@ -2192,7 +2269,7 @@ module Squared
2192
2269
  end
2193
2270
 
2194
2271
  def color(val)
2195
- (ret = theme[val]) && !ret.empty? ? ret : [val]
2272
+ (ret = Array(theme[val])).empty? ? [val] : ret
2196
2273
  end
2197
2274
 
2198
2275
  def colormap(val)
@@ -2201,7 +2278,7 @@ module Squared
2201
2278
 
2202
2279
  def verbosetype
2203
2280
  case verbose
2204
- when TrueClass
2281
+ when true
2205
2282
  1
2206
2283
  when Numeric
2207
2284
  verbose.succ
@@ -2211,9 +2288,15 @@ module Squared
2211
2288
  end
2212
2289
 
2213
2290
  def on(event, from, *args, **kwargs)
2214
- return unless from && @events.key?(event)
2291
+ return unless from && @on[:event].key?(event)
2215
2292
 
2216
- Array(@events[event][from]).each do |obj|
2293
+ case event
2294
+ when :first
2295
+ @on[:from] << from
2296
+ when :last
2297
+ return unless @on[:from].delete(from)
2298
+ end
2299
+ Array(@on[:event][event][from]).each do |obj|
2217
2300
  target, opts = if obj.is_a?(Array) && obj[1].is_a?(Hash)
2218
2301
  [obj[0], kwargs.empty? ? obj[1] : obj[1].merge(kwargs)]
2219
2302
  else
@@ -2230,28 +2313,51 @@ module Squared
2230
2313
  end
2231
2314
  end
2232
2315
 
2233
- def on_error(err, from, pass: false, exception: self.exception, dryrun: false)
2316
+ def on_error(err, from = @on[:from].last, exception: exception?, fatal: true, dryrun: false, pass: false)
2234
2317
  log&.error err
2235
- unless dryrun
2236
- ret = on :error, from, err
2237
- raise err if exception && ret != true
2318
+ unless from == false
2319
+ cancel = -> { @on[:from] = [false] if fatal && @on[:from].include?(from) }
2320
+ if dryrun
2321
+ cancel.call
2322
+ else
2323
+ ret = on :error, from, err
2324
+ unless ret == true
2325
+ cancel.call
2326
+ raise err if exception
2327
+ end
2328
+ end
2238
2329
  end
2239
2330
  print_error(err, pass: pass) unless ret
2240
2331
  end
2241
2332
 
2242
- def pwd_set(pass: false, exception: self.exception, dryrun: false, from: nil)
2243
- return yield if (path.to_s == Dir.pwd || pass == true) && (workspace.mri? || !workspace.windows?)
2333
+ def pwd_set(cmd = nil, pass: false, timeout: nil, from: @on[:from].last, **kwargs, &blk)
2334
+ return if from == false
2244
2335
 
2245
- pwd = Dir.pwd
2246
- Dir.chdir(path)
2247
- yield
2248
- rescue StandardError => e
2249
- on_error(e, from, exception: exception, dryrun: dryrun)
2336
+ require 'timeout'
2337
+ unless (path.to_s == Dir.pwd || pass == true) && (workspace.mri? || !workspace.windows?)
2338
+ pwd = Dir.pwd
2339
+ Dir.chdir(path)
2340
+ end
2341
+ timeout = session_timeout cmd if cmd && timeout.nil?
2342
+ if timeout.to_f > 0
2343
+ Timeout.timeout(timeout, &blk)
2344
+ else
2345
+ yield
2346
+ end
2347
+ rescue Timeout::Error => e
2348
+ if cmd
2349
+ print_error(Logger::ERROR, cmd, subject: name, hint: e)
2350
+ else
2351
+ print_error(Logger::ERROR, e, subject: name)
2352
+ end
2353
+ exit 1 unless exception?(Logger::DEBUG, Logger::INFO)
2354
+ rescue => e
2355
+ on_error(e, from, **kwargs)
2250
2356
  ensure
2251
2357
  Dir.chdir(pwd) if pwd
2252
2358
  end
2253
2359
 
2254
- def run_set(cmd, val = nil, opts: nil, global: false, **)
2360
+ def run_set(cmd, val = nil, opts: nil, global: nil, **)
2255
2361
  noopt = @output[1] == false && !@output[0].nil?
2256
2362
  noenv = @output[2] == false
2257
2363
  parse = lambda do |data|
@@ -2270,7 +2376,14 @@ module Squared
2270
2376
  ret[2] = data[:env] unless dise
2271
2377
  ret
2272
2378
  end
2273
- self.global = global
2379
+ case global
2380
+ when true
2381
+ self.global = 1
2382
+ when false
2383
+ self.global = -1
2384
+ when Numeric
2385
+ self.global = 1 | global
2386
+ end
2274
2387
  case cmd
2275
2388
  when Hash
2276
2389
  @output = parse.call(data)
@@ -2302,12 +2415,19 @@ module Squared
2302
2415
  end
2303
2416
  end
2304
2417
 
2305
- def script_set(cmd, prod: nil, args: nil, global: false, **)
2418
+ def script_set(cmd, prod: nil, args: nil, global: nil, **)
2306
2419
  return if @output[1] == false && @output[0].nil?
2307
2420
 
2308
- self.global = global
2421
+ case global
2422
+ when true
2423
+ self.global = 2
2424
+ when false
2425
+ self.global = -2
2426
+ when Numeric
2427
+ self.global = 2 | global
2428
+ end
2309
2429
  @output[0] = nil
2310
- @output[1] = if self.global && cmd.is_a?(Array)
2430
+ @output[1] = if global && cmd.is_a?(Array)
2311
2431
  cmd[prod == true ? 1 : 0]
2312
2432
  else
2313
2433
  cmd
@@ -2329,6 +2449,15 @@ module Squared
2329
2449
  end
2330
2450
  end
2331
2451
 
2452
+ def timeout_set(val)
2453
+ case val
2454
+ when Hash
2455
+ @timeout.update(val)
2456
+ when Numeric
2457
+ @timeout[:_][2] = val
2458
+ end
2459
+ end
2460
+
2332
2461
  def pass_set(val)
2333
2462
  @pass = Array(val).freeze
2334
2463
  end
@@ -2376,8 +2505,8 @@ module Squared
2376
2505
  end
2377
2506
  end
2378
2507
 
2379
- def as_get(val, from)
2380
- (global && @as[from][val]) || val
2508
+ def as_get(val, from, *type)
2509
+ (global(*type) && @as[from][val]) || val
2381
2510
  end
2382
2511
 
2383
2512
  def unpack_get(*)
@@ -2400,7 +2529,7 @@ module Squared
2400
2529
  next if (items = children.select { |item| item.task_include?(key) }).empty?
2401
2530
 
2402
2531
  ws.task_desc(@desc, action, 'workspace')
2403
- task task_join(action, 'workspace') => items.map! { |item| task_join(item.name, action) }
2532
+ task task_join(action, 'workspace') => items.map { |item| task_join(item.name, action) }
2404
2533
  end
2405
2534
  end
2406
2535
  end
@@ -2409,10 +2538,6 @@ module Squared
2409
2538
  @only ? !@only.include?(key) : @pass.include?(key)
2410
2539
  end
2411
2540
 
2412
- def matchany?(val, list, empty: true)
2413
- list.empty? ? empty : list.any? { |pat| val.match?(pat) }
2414
- end
2415
-
2416
2541
  def projectpath?(val)
2417
2542
  ret = Pathname.new(val).cleanpath
2418
2543
  ret.absolute? ? ret.to_s.start_with?(File.join(path, '')) : !ret.to_s.start_with?(File.join('..', ''))
@@ -2425,10 +2550,6 @@ module Squared
2425
2550
  false
2426
2551
  end
2427
2552
 
2428
- def semmajor?(cur, want)
2429
- (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
2430
- end
2431
-
2432
2553
  def printfirst?
2433
2554
  @@print_order == 0
2434
2555
  end
@@ -2470,26 +2591,24 @@ module Squared
2470
2591
  end
2471
2592
 
2472
2593
  def success?(run, *cond)
2473
- case run
2474
- when TrueClass
2475
- true
2476
- when FalseClass
2477
- false
2478
- else
2479
- $?.success?
2480
- end.tap do |ret|
2481
- next unless cond.none? { |val| val == false }
2482
-
2594
+ ret = case run
2595
+ when true, false
2596
+ run
2597
+ else
2598
+ $?.success?
2599
+ end
2600
+ if cond.none? { |val| val == false }
2483
2601
  if block_given?
2484
2602
  yield ret
2485
2603
  elsif ret && stdout? && banner?
2486
2604
  print_success
2487
2605
  end
2488
2606
  end
2607
+ ret
2489
2608
  end
2490
2609
 
2491
2610
  def banner?
2492
- ARG[:BANNER] && !env('BANNER', equals: '0')
2611
+ ARG[:BANNER] && env('BANNER', notequals: '0')
2493
2612
  end
2494
2613
 
2495
2614
  def pwd?
@@ -2516,29 +2635,9 @@ module Squared
2516
2635
  workspace.warning
2517
2636
  end
2518
2637
 
2519
- def has_value?(target, *args)
2520
- return false unless target.is_a?(Enumerable)
2521
-
2522
- args = args.first if args.size == 1 && args.first.is_a?(Enumerable)
2523
- args.any? { |obj,| target.include?(obj) }
2524
- end
2525
-
2526
- def has_value!(target, *args, first: false)
2527
- return unless target.is_a?(Enumerable)
2528
-
2529
- args = args.first if args.size == 1 && args.first.is_a?(Enumerable)
2530
- found = false
2531
- args.each do |val|
2532
- if target.respond_to?(:delete?)
2533
- found = true if target.delete?(val)
2534
- elsif target.respond_to?(:delete)
2535
- found = true if target.delete(val)
2536
- elsif target.include?(val)
2537
- found = true
2538
- end
2539
- break if found && first
2540
- end
2541
- target if found
2638
+ def exception?(*level)
2639
+ ex = exception
2640
+ level.empty? ? ex != false && ex != Logger::INFO : ex.is_a?(Numeric) && level.include?(ex)
2542
2641
  end
2543
2642
 
2544
2643
  def variables
@@ -2558,12 +2657,13 @@ module Squared
2558
2657
  end
2559
2658
 
2560
2659
  def scriptargs
2561
- { target: script? ? @output[1] : @output[0], script: script?, ref: ref, group: group, global: global }
2660
+ n = script? ? 1 : 0
2661
+ { target: @output[n], type: n == 1 ? :script : :run, ref: ref, group: group, global: global(n.succ) }
2562
2662
  end
2563
2663
  end
2564
2664
 
2565
- Application.implement(Base, base: true)
2566
- Application.attr_banner = Set.new(%i[name project path ref group parent])
2665
+ Application.implement Base, base: 0
2666
+ Application.attr_banner.merge(%i[name project path ref group parent])
2567
2667
  end
2568
2668
  end
2569
2669
  end