squared 0.6.10 → 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
 
@@ -1918,71 +2017,50 @@ module Squared
1918
2017
  args
1919
2018
  end
1920
2019
 
1921
- def confirm_basic(msg, hint, default = 'Y', style: :inline, target: @session, prefix: nil, **kwargs)
1922
- return true if prefix ? option('y', prefix: prefix) : target && option('y', target: target)
1923
-
1924
- confirm("#{msg} [#{sub_style(hint.to_s, style.is_a?(Symbol) ? theme[style] : style)}]", default, **kwargs)
1925
- end
1926
-
1927
- def confirm_outdated(pkg, ver, type, cur = nil, lock: false, col0: 0, col1: 0, col2: nil, col3: 0, col4: 0,
1928
- **kwargs)
1929
- h = sub_style(semrev(type).upcase, (type == 1 && theme[:major]) || theme[:header])
1930
- case col0
1931
- when 0
1932
- col0 = "#{h}: "
1933
- when Numeric
1934
- puts h
1935
- col0 = ' ' * col0
1936
- else
1937
- puts h
1938
- end
1939
- b = sub_style pkg.ljust(col1), theme[:inline]
1940
- cur ||= 'locked' if lock
1941
- c = if cur
1942
- cur = cur.ljust(col2 || cur.size.succ)
1943
- lock ? sub_style(cur, color(:red)) : cur
1944
- end
1945
- d = type == 1 || lock ? 'N' : 'Y'
1946
- e = "#{col0}#{b}#{c}#{sub_style(col1 > 0 ? ver.ljust(col3) : ver.rjust(ver.size.succ), theme[:inline])}"
1947
- confirm("#{e}#{col4 > 0 ? ' ' * [col4 - e.stripstyle.size - 1, 2].max : ' '}", d, **kwargs)
2020
+ def confirm_basic(msg, target, default = 'Y', style: :inline, **kwargs)
2021
+ confirm("#{msg} [#{sub_style(target.to_s, style.is_a?(Symbol) ? theme[style] : style)}]", default, **kwargs)
1948
2022
  end
1949
2023
 
1950
2024
  def confirm_semver(msg, type, style: (type == 1 && theme[:major]) || :inline, timeout: 0, **kwargs)
1951
- 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)
1952
2034
  end
1953
2035
 
1954
2036
  def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil, multiple: false,
1955
2037
  force: true, **kwargs)
1956
2038
  puts unless series || printfirst?
1957
- ret = choice(msg, list, multiple: multiple, force: force, **kwargs).tap do |val|
1958
- next unless !val || val.empty?
1959
-
2039
+ ret = choice(msg, list, multiple: multiple, force: force, **kwargs)
2040
+ unless ret && !ret.empty?
1960
2041
  exit 1 if force
1961
- return nil
2042
+ return
1962
2043
  end
1963
- 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
1964
2045
  if column
1965
2046
  a, b = Array(column)
1966
- ret = Array(ret).map! { |val| val[a, b || 1] }
2047
+ ret = Array(ret).map { |val| val[a, b || 1] }
1967
2048
  ret = ret.first unless multiple
1968
2049
  end
1969
2050
  if accept
1970
2051
  hint = Array(ret).map { |val| sub_style(val.to_s, theme[:inline]) }.join(', ')
1971
2052
  accept = Array(accept).map { |val| Array(val) }
1972
2053
  ret = Array(ret) if accept.any? { |val| val[1] == true }
1973
- loop do
1974
- item = accept.first
2054
+ begin
2055
+ item = accept.shift
1975
2056
  c = confirm("#{item[0]}#{" [#{hint}]" if hint}", item[2] ? 'Y' : 'N')
1976
2057
  if item[1] == true
1977
2058
  ret << c
1978
2059
  elsif !c
1979
- break
2060
+ exit 1
1980
2061
  end
1981
2062
  hint = nil
1982
- accept.shift
1983
- break if accept.empty?
1984
- end
1985
- exit 1 unless accept.empty?
2063
+ end until accept.empty?
1986
2064
  end
1987
2065
  if values
1988
2066
  ret = Array(ret)
@@ -1993,7 +2071,7 @@ module Squared
1993
2071
  force = false
1994
2072
  end
1995
2073
  val = readline(val, force: force)
1996
- ret << (val unless val.empty?)
2074
+ ret << (val.empty? ? nil : val)
1997
2075
  end
1998
2076
  end
1999
2077
  printsucc unless series
@@ -2011,10 +2089,11 @@ module Squared
2011
2089
  def command_args(args, min: 0, force: false, **kwargs)
2012
2090
  return if args.size > min || option('i', 'interactive', **kwargs, equals: '0')
2013
2091
 
2014
- readline('Enter arguments', force: force)
2092
+ ret = readline('Enter arguments', force: force)
2093
+ args << ret unless ret.empty?
2015
2094
  end
2016
2095
 
2017
- def block_args(fallback = nil, &blk)
2096
+ def block_args(fallback = [], &blk)
2018
2097
  return fallback if (ret = instance_eval(&blk)).nil?
2019
2098
 
2020
2099
  Array(ret)
@@ -2033,33 +2112,40 @@ module Squared
2033
2112
  if workspace.powershell?
2034
2113
  "#{shell_bin('powershell.exe')} -Command \"& {#{args.join(' ; ')}}\"#{out}"
2035
2114
  else
2036
- args.map! { |val| "#{val}#{out}" }.join(' && ')
2115
+ args.map { |val| "#{val}#{out}" }.join(' && ')
2037
2116
  end
2038
2117
  end
2039
2118
 
2040
2119
  def relativepath(*list, all: false)
2041
- list.flatten.map! { |val| Pathname.new(val) }.select { |val| projectpath?(val) }.map! do |val|
2042
- ret = (val.absolute? ? val.relative_path_from(path) : val.cleanpath).to_s
2043
- all && val.to_s.end_with?('/') ? "#{ret}/*" : ret
2044
- 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
2045
2126
  end
2046
2127
 
2047
- def projectmap(files, parent: false, pass: true)
2128
+ def projectmap(files, resolve: true, parent: false, pass: true)
2048
2129
  unless parent
2049
- proj = files.select { |val| projectpath?(val) }
2050
- raise_error 'pathspec not within worktree' unless pass || files.size == proj.size
2051
- 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
2052
2144
  end
2053
- files.map { |val| val == '.' ? '.' : shell_quote(basepath(val)) }
2054
2145
  end
2055
2146
 
2056
- def matchmap(list, prefix = nil)
2057
- list.map do |val|
2058
- next val if val.is_a?(Regexp)
2059
-
2060
- val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
2061
- Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
2062
- end
2147
+ def symjoin(*args, char: ':')
2148
+ args.join(char).to_sym
2063
2149
  end
2064
2150
 
2065
2151
  def semver(val)
@@ -2084,10 +2170,10 @@ module Squared
2084
2170
  return -1 if (b = other.scan(SEM_VER)).empty?
2085
2171
  return 1 if (a = val.scan(SEM_VER)).empty?
2086
2172
 
2087
- a, b = [a.first, b.first].map! do |c|
2173
+ a, b = [a.first, b.first].map do |c|
2088
2174
  d = begin
2089
2175
  Integer(c[5]).to_s
2090
- rescue StandardError
2176
+ rescue
2091
2177
  c[5] ? '-1' : '0'
2092
2178
  end
2093
2179
  [c[0], c[2], c[4] || '0', d]
@@ -2125,24 +2211,13 @@ module Squared
2125
2211
  end
2126
2212
 
2127
2213
  def semtype(cur, lat)
2128
- if semmajor?(cur, lat)
2129
- 1
2130
- else
2131
- cur[2] == lat[2] ? 3 : 2
2132
- end
2214
+ return 1 if semmajor?(cur, lat)
2215
+
2216
+ cur[2] == lat[2] ? 3 : 2
2133
2217
  end
2134
2218
 
2135
- def semrev(type)
2136
- case type
2137
- when 1
2138
- 'major'
2139
- when 2
2140
- 'minor'
2141
- when 3
2142
- 'patch'
2143
- else
2144
- 'unknown'
2145
- end
2219
+ def semmajor?(cur, want)
2220
+ (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
2146
2221
  end
2147
2222
 
2148
2223
  def semgte?(val, other = nil)
@@ -2168,7 +2243,7 @@ module Squared
2168
2243
  def shortname(*args, suffix: '?', delim: ',', pass: false)
2169
2244
  return unless TASK_METADATA || pass
2170
2245
 
2171
- args.map! do |ch|
2246
+ args.map do |ch|
2172
2247
  "#{ch}/#{case ch
2173
2248
  when 'i'
2174
2249
  'nteractive'
@@ -2194,7 +2269,7 @@ module Squared
2194
2269
  end
2195
2270
 
2196
2271
  def color(val)
2197
- (ret = theme[val]) && !ret.empty? ? ret : [val]
2272
+ (ret = Array(theme[val])).empty? ? [val] : ret
2198
2273
  end
2199
2274
 
2200
2275
  def colormap(val)
@@ -2203,7 +2278,7 @@ module Squared
2203
2278
 
2204
2279
  def verbosetype
2205
2280
  case verbose
2206
- when TrueClass
2281
+ when true
2207
2282
  1
2208
2283
  when Numeric
2209
2284
  verbose.succ
@@ -2213,9 +2288,15 @@ module Squared
2213
2288
  end
2214
2289
 
2215
2290
  def on(event, from, *args, **kwargs)
2216
- return unless from && @events.key?(event)
2291
+ return unless from && @on[:event].key?(event)
2217
2292
 
2218
- 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|
2219
2300
  target, opts = if obj.is_a?(Array) && obj[1].is_a?(Hash)
2220
2301
  [obj[0], kwargs.empty? ? obj[1] : obj[1].merge(kwargs)]
2221
2302
  else
@@ -2232,28 +2313,51 @@ module Squared
2232
2313
  end
2233
2314
  end
2234
2315
 
2235
- 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)
2236
2317
  log&.error err
2237
- unless dryrun
2238
- ret = on :error, from, err
2239
- 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
2240
2329
  end
2241
2330
  print_error(err, pass: pass) unless ret
2242
2331
  end
2243
2332
 
2244
- def pwd_set(pass: false, exception: self.exception, dryrun: false, from: nil)
2245
- 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
2246
2335
 
2247
- pwd = Dir.pwd
2248
- Dir.chdir(path)
2249
- yield
2250
- rescue StandardError => e
2251
- 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)
2252
2356
  ensure
2253
2357
  Dir.chdir(pwd) if pwd
2254
2358
  end
2255
2359
 
2256
- def run_set(cmd, val = nil, opts: nil, global: false, **)
2360
+ def run_set(cmd, val = nil, opts: nil, global: nil, **)
2257
2361
  noopt = @output[1] == false && !@output[0].nil?
2258
2362
  noenv = @output[2] == false
2259
2363
  parse = lambda do |data|
@@ -2272,7 +2376,14 @@ module Squared
2272
2376
  ret[2] = data[:env] unless dise
2273
2377
  ret
2274
2378
  end
2275
- 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
2276
2387
  case cmd
2277
2388
  when Hash
2278
2389
  @output = parse.call(data)
@@ -2304,12 +2415,19 @@ module Squared
2304
2415
  end
2305
2416
  end
2306
2417
 
2307
- def script_set(cmd, prod: nil, args: nil, global: false, **)
2418
+ def script_set(cmd, prod: nil, args: nil, global: nil, **)
2308
2419
  return if @output[1] == false && @output[0].nil?
2309
2420
 
2310
- 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
2311
2429
  @output[0] = nil
2312
- @output[1] = if self.global && cmd.is_a?(Array)
2430
+ @output[1] = if global && cmd.is_a?(Array)
2313
2431
  cmd[prod == true ? 1 : 0]
2314
2432
  else
2315
2433
  cmd
@@ -2331,6 +2449,15 @@ module Squared
2331
2449
  end
2332
2450
  end
2333
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
+
2334
2461
  def pass_set(val)
2335
2462
  @pass = Array(val).freeze
2336
2463
  end
@@ -2378,8 +2505,8 @@ module Squared
2378
2505
  end
2379
2506
  end
2380
2507
 
2381
- def as_get(val, from)
2382
- (global && @as[from][val]) || val
2508
+ def as_get(val, from, *type)
2509
+ (global(*type) && @as[from][val]) || val
2383
2510
  end
2384
2511
 
2385
2512
  def unpack_get(*)
@@ -2402,7 +2529,7 @@ module Squared
2402
2529
  next if (items = children.select { |item| item.task_include?(key) }).empty?
2403
2530
 
2404
2531
  ws.task_desc(@desc, action, 'workspace')
2405
- 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) }
2406
2533
  end
2407
2534
  end
2408
2535
  end
@@ -2411,10 +2538,6 @@ module Squared
2411
2538
  @only ? !@only.include?(key) : @pass.include?(key)
2412
2539
  end
2413
2540
 
2414
- def matchany?(val, list, empty: true)
2415
- list.empty? ? empty : list.any? { |pat| val.match?(pat) }
2416
- end
2417
-
2418
2541
  def projectpath?(val)
2419
2542
  ret = Pathname.new(val).cleanpath
2420
2543
  ret.absolute? ? ret.to_s.start_with?(File.join(path, '')) : !ret.to_s.start_with?(File.join('..', ''))
@@ -2427,10 +2550,6 @@ module Squared
2427
2550
  false
2428
2551
  end
2429
2552
 
2430
- def semmajor?(cur, want)
2431
- (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
2432
- end
2433
-
2434
2553
  def printfirst?
2435
2554
  @@print_order == 0
2436
2555
  end
@@ -2472,26 +2591,24 @@ module Squared
2472
2591
  end
2473
2592
 
2474
2593
  def success?(run, *cond)
2475
- case run
2476
- when TrueClass
2477
- true
2478
- when FalseClass
2479
- false
2480
- else
2481
- $?.success?
2482
- end.tap do |ret|
2483
- next unless cond.none? { |val| val == false }
2484
-
2594
+ ret = case run
2595
+ when true, false
2596
+ run
2597
+ else
2598
+ $?.success?
2599
+ end
2600
+ if cond.none? { |val| val == false }
2485
2601
  if block_given?
2486
2602
  yield ret
2487
2603
  elsif ret && stdout? && banner?
2488
2604
  print_success
2489
2605
  end
2490
2606
  end
2607
+ ret
2491
2608
  end
2492
2609
 
2493
2610
  def banner?
2494
- ARG[:BANNER] && !env('BANNER', equals: '0')
2611
+ ARG[:BANNER] && env('BANNER', notequals: '0')
2495
2612
  end
2496
2613
 
2497
2614
  def pwd?
@@ -2518,29 +2635,9 @@ module Squared
2518
2635
  workspace.warning
2519
2636
  end
2520
2637
 
2521
- def has_value?(target, *args)
2522
- return false unless target.is_a?(Enumerable)
2523
-
2524
- args = args.first if args.size == 1 && args.first.is_a?(Enumerable)
2525
- args.any? { |obj,| target.include?(obj) }
2526
- end
2527
-
2528
- def has_value!(target, *args, first: false)
2529
- return unless target.is_a?(Enumerable)
2530
-
2531
- args = args.first if args.size == 1 && args.first.is_a?(Enumerable)
2532
- found = false
2533
- args.each do |val|
2534
- if target.respond_to?(:delete?)
2535
- found = true if target.delete?(val)
2536
- elsif target.respond_to?(:delete)
2537
- found = true if target.delete(val)
2538
- elsif target.include?(val)
2539
- found = true
2540
- end
2541
- break if found && first
2542
- end
2543
- 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)
2544
2641
  end
2545
2642
 
2546
2643
  def variables
@@ -2560,12 +2657,13 @@ module Squared
2560
2657
  end
2561
2658
 
2562
2659
  def scriptargs
2563
- { 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) }
2564
2662
  end
2565
2663
  end
2566
2664
 
2567
- Application.implement(Base, base: true)
2568
- 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])
2569
2667
  end
2570
2668
  end
2571
2669
  end