squared 0.6.21 → 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,14 +17,15 @@ 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
- SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+))?(?:([-.])?(\S+))?\b/.freeze
28
+ SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+))?[-.]?(\S+)?\b/.freeze
27
29
  URI_SCHEME = %r{\A([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
28
30
  TASK_METADATA = Rake::TaskManager.record_task_metadata
29
31
  private_constant :OPTIONS, :VAR_SET, :BLK_SET, :SEM_VER, :URI_SCHEME, :TASK_METADATA
@@ -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) { |s| log[:level] = s.start_with?(/\d/) ? log_sym(s.to_i) : s }
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
 
@@ -521,10 +431,9 @@ module Squared
521
431
  args[0] = instance_eval(&blk) || f
522
432
  return unless args.first
523
433
  end
524
- if args.all?(Array)
525
- cmd = []
434
+ if args.all? { |val| val.is_a?(Array) }
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]
@@ -841,7 +747,7 @@ module Squared
841
747
  ensure
842
748
  if dir
843
749
  remove_entry dir
844
- elsif delete && file.exist?
750
+ elsif delete && file&.exist?
845
751
  file.unlink
846
752
  end
847
753
  end
@@ -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, send: :system, **)
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, name: send, 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
@@ -1008,7 +969,7 @@ module Squared
1008
969
  run_set(output[0], *args, **kwargs)
1009
970
  when :dependfile
1010
971
  @dependindex = nil
1011
- @dependfile = (basepath(*args) if val)
972
+ @dependfile = val.nil? ? nil : basepath(*args)
1012
973
  else
1013
974
  instance_variable_set(:"@#{key}", val)
1014
975
  end
@@ -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)
@@ -1116,7 +1077,7 @@ module Squared
1116
1077
  end
1117
1078
 
1118
1079
  def dependtype(*)
1119
- dependindex&.succ || 0
1080
+ @dependindex ? @dependindex.succ : 0
1120
1081
  end
1121
1082
 
1122
1083
  def dependname
@@ -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)
@@ -1168,20 +1129,10 @@ module Squared
1168
1129
  ret = basepath(*args)
1169
1130
  return ret unless ascend && !ret.exist?
1170
1131
 
1171
- case ascend
1172
- when String, Pathname
1173
- root = Pathname.new(ascend).cleanpath
1174
- root = nil unless root.absolute? && root.directory?
1175
- end
1176
1132
  path.parent.ascend.each do |dir|
1177
1133
  target = dir.join(*args)
1178
1134
  return target if target.exist?
1179
-
1180
- if root
1181
- break if dir == root
1182
- elsif workspace.root == dir || parent&.path == dir || (ascend.is_a?(String) && dir.join(ascend).exist?)
1183
- break
1184
- end
1135
+ break if (ascend.is_a?(String) && dir.join(ascend).exist?) || workspace.root == dir || parent&.path == dir
1185
1136
  end
1186
1137
  ret
1187
1138
  end
@@ -1193,7 +1144,12 @@ module Squared
1193
1144
  def scriptname(from: :run)
1194
1145
  return unless (name = @output[1]) && respond_to?(:compose)
1195
1146
 
1196
- 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)
1197
1153
  end
1198
1154
 
1199
1155
  def inspect
@@ -1212,21 +1168,123 @@ module Squared
1212
1168
 
1213
1169
  def script_get(*args, key: nil)
1214
1170
  ret = workspace.script_get(*args, group: group, ref: allref)
1215
- return ret unless ret && key
1171
+ ret && key ? ret.fetch(key, nil) : ret
1172
+ end
1216
1173
 
1217
- ret.fetch(key, nil)
1174
+ def index_get
1175
+ @index
1176
+ end
1177
+
1178
+ def envname_get
1179
+ @envname
1180
+ end
1181
+
1182
+ def graph_get
1183
+ @graph
1218
1184
  end
1219
1185
 
1220
1186
  private
1221
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
+
1222
1281
  def puts(*args, **kwargs)
1223
1282
  log_console(*args, pipe: kwargs[:pipe] || pipe)
1224
1283
  end
1225
1284
 
1226
1285
  def run_s(*cmd, sync: true, banner: !silent?, from: nil, **kwargs)
1227
- cmd.flatten!
1228
1286
  case cmd.last
1229
- when Hash, TrueClass, FalseClass
1287
+ when Hash, true, false
1230
1288
  var = cmd.pop
1231
1289
  end
1232
1290
  on :first, from
@@ -1235,8 +1293,8 @@ module Squared
1235
1293
  print_run val, banner
1236
1294
  run(val, var, sync: sync, banner: banner, **kwargs)
1237
1295
  end
1238
- rescue StandardError => e
1239
- on_error(e, from, exception: kwargs.fetch(:exception, exception))
1296
+ rescue => e
1297
+ on_error(e, exception: kwargs.fetch(:exception, exception?))
1240
1298
  end
1241
1299
  on :last, from
1242
1300
  end
@@ -1250,11 +1308,8 @@ module Squared
1250
1308
  when Proc
1251
1309
  instance_eval(&obj)
1252
1310
  when Method
1253
- args = if (n = obj.arity.abs) > 0
1254
- Array.new(n).tap { |data| data[0] = self }
1255
- else
1256
- []
1257
- end
1311
+ n = obj.arity.abs
1312
+ args = n > 0 ? Array.new(n).tap { |data| data[0] = self } : []
1258
1313
  obj.call(*args)
1259
1314
  else
1260
1315
  if series?(obj)
@@ -1267,8 +1322,25 @@ module Squared
1267
1322
  end
1268
1323
  end
1269
1324
 
1270
- def graph_branch(target, data, tasks = nil, out = nil, sync: true, pass: [], done: [], order: nil, depth: 0,
1271
- 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)
1272
1344
  tag = ->(proj) { "#{proj.name}#{"@#{proj.version}" if SEM_VER.match?(proj.version)}" }
1273
1345
  uniq = lambda do |name|
1274
1346
  return [] unless (ret = data[name])
@@ -1304,6 +1376,7 @@ module Squared
1304
1376
  "#{single ? ' ' : a}#{' ' * depth.pred}#{last ? d : c}#{b * 3}#{items.empty? ? b : e} #{f}"
1305
1377
  end
1306
1378
  end
1379
+ ctx = { sync: sync, pass: pass, done: done, order: order, single: single, depth: depth.succ, context: target }
1307
1380
  items.each_with_index do |proj, i|
1308
1381
  next if done.include?(proj)
1309
1382
 
@@ -1316,8 +1389,7 @@ module Squared
1316
1389
  end
1317
1390
  end
1318
1391
  unless target.name == name || (none = (t - done).empty?)
1319
- graph_branch(proj, data, tasks, out, sync: sync, pass: pass, done: done, order: order, depth: depth.succ,
1320
- single: single, last: j == true, context: target)
1392
+ graph_branch(proj, data, tasks, out, last: j == true, **ctx)
1321
1393
  end
1322
1394
  if out
1323
1395
  if none
@@ -1330,20 +1402,20 @@ module Squared
1330
1402
  final = data.keys.last
1331
1403
  while k < depth
1332
1404
  indent = k > 0 ? ((last && !j) || (j && k == depth.pred) || single) : j && last && depth == 1
1333
- s += "#{indent || (last && data[final].last == context) ? ' ' : a} "
1405
+ s += "#{indent || (last && data[final].last == kwargs[:context]) ? ' ' : a} "
1334
1406
  k += 1
1335
1407
  end
1336
- s + "#{j ? d : c}#{b * 3} #{tag.call(proj)}"
1408
+ s += "#{j ? d : c}#{b * 3} #{tag.call(proj)}"
1337
1409
  end
1338
1410
  end
1339
1411
  if order
1340
1412
  n = order.size
1341
1413
  order[name] ||= if proj.parent
1342
- if order[key = proj.parent.name]
1343
- order[key] += 1
1414
+ if order[s = proj.parent.name]
1415
+ order[s] += 1
1344
1416
  n.pred
1345
1417
  else
1346
- order[key] = n.succ
1418
+ order[s] = n.succ
1347
1419
  n
1348
1420
  end
1349
1421
  else
@@ -1356,14 +1428,14 @@ module Squared
1356
1428
  next if pass.include?(meth)
1357
1429
 
1358
1430
  if workspace.task_defined?(cmd = task_join(name, meth))
1359
- if ENV.key?(key = "BANNER_#{name.upcase}")
1431
+ if ENV.key?(key = "BANNER_#{envname_get}")
1360
1432
  key = nil
1361
1433
  else
1362
1434
  ENV[key] = '0'
1363
1435
  end
1364
1436
  run(cmd, sync: false, banner: false)
1365
1437
  ENV.delete(key) if key
1366
- elsif proj.has?(meth, (workspace.baseref unless tasks || graph))
1438
+ elsif proj.has?(meth, tasks || graph ? nil : workspace.baseref)
1367
1439
  proj.__send__(meth.to_sym, sync: sync)
1368
1440
  end
1369
1441
  end
@@ -1374,8 +1446,7 @@ module Squared
1374
1446
  end
1375
1447
 
1376
1448
  def graph_collect(target, start = [], data: {}, pass: [], root: [])
1377
- deps = []
1378
- (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|
1379
1450
  next if pass.include?(val)
1380
1451
 
1381
1452
  if (obj = workspace.find(name: val))
@@ -1390,8 +1461,8 @@ module Squared
1390
1461
  end
1391
1462
  next if (objs = data.fetch(name, [])).include?(target)
1392
1463
 
1393
- deps << proj
1394
- deps.concat(objs)
1464
+ out << proj
1465
+ out.concat(objs)
1395
1466
  end
1396
1467
  end
1397
1468
  deps.uniq!
@@ -1402,39 +1473,31 @@ module Squared
1402
1473
 
1403
1474
  def graph_deps(target = self)
1404
1475
  key = target.name
1405
- return @@graph[key] if @@graph.key?(key)
1406
-
1407
- base = []
1408
- deps = []
1409
- loop do
1410
- deps.concat(graph_branch(target, graph_collect(target), []))
1411
- 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)
1412
1482
 
1413
- base << target
1483
+ base << target
1484
+ end
1485
+ deps.uniq!
1486
+ [base, deps]
1414
1487
  end
1415
- deps.uniq!
1416
- @@graph[key] = [base, deps]
1417
1488
  end
1418
1489
 
1419
- def env(key, default = nil, suffix: nil, equals: nil, ignore: nil, strict: false)
1420
- name = "#{key}_#{@envname}"
1490
+ def env(key, default = nil, suffix: nil, strict: false, ignore: nil, **kwargs, &blk)
1491
+ name = "#{key}_#{envname_get}"
1421
1492
  ret = if suffix
1422
- ENV["#{name}_#{suffix}"] || (ignore == false && ENV["#{key}_#{suffix}"]) || ''
1493
+ ENV.fetch("#{name}_#{suffix}", '')
1423
1494
  elsif strict
1424
1495
  ENV[name].to_s
1425
1496
  else
1426
1497
  ignore = ['0'].freeze if ignore.nil?
1427
1498
  ENV[name] || ENV.fetch(key, '')
1428
1499
  end
1429
- if equals.nil?
1430
- ret = default if ret.empty? || (ignore && Array(ignore).any? { |val| ret == val.to_s })
1431
- return ret if ret.nil?
1432
- else
1433
- ret = Array(equals).any? { |val| ret == val.to_s }
1434
- end
1435
- return yield ret if block_given?
1436
-
1437
- ret
1500
+ env_yield(ret, default, ignore: ignore, **kwargs, &blk)
1438
1501
  end
1439
1502
 
1440
1503
  def session(*cmd, prefix: cmd.first, main: true, path: true, options: true)
@@ -1442,7 +1505,7 @@ module Squared
1442
1505
  if path && (val = shell_bin(prefix))
1443
1506
  cmd[0] = shell_quote(val, force: false)
1444
1507
  end
1445
- ret = JoinSet.new(cmd.flatten(1))
1508
+ ret = JoinSet.new(cmd)
1446
1509
  if options
1447
1510
  env("#{prefix.upcase}_OPTIONS") do |val|
1448
1511
  if val.start_with?('-')
@@ -1452,9 +1515,17 @@ module Squared
1452
1515
  end
1453
1516
  end
1454
1517
  end
1455
- 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
1456
1526
 
1457
- @session = ret
1527
+ def session_timeout(cmd)
1528
+ @timeout[:_][0][cmd] || @timeout[:_][2]
1458
1529
  end
1459
1530
 
1460
1531
  def session_output(*cmd, **kwargs)
@@ -1478,18 +1549,18 @@ module Squared
1478
1549
  end
1479
1550
 
1480
1551
  def session_apply(val, args: nil, kwargs: nil, pass: [], keys: [:opts], exclude: [])
1481
- a = []
1482
1552
  b = {}
1483
- Array(val).each do |c|
1553
+ a = Array(val).each_with_object([]) do |c, out|
1484
1554
  d, e, f = session_get c
1485
- unless (f -= exclude).empty?
1555
+ f -= exclude
1556
+ unless f.empty?
1486
1557
  h = []
1487
1558
  i = {}
1488
- session_apply(f.map!(&:to_s), args: h, kwargs: i, pass: pass, keys: keys, exclude: exclude.concat(f))
1489
- 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)
1490
1561
  append_keys(b, i, *keys)
1491
1562
  end
1492
- a.concat(d)
1563
+ out.concat(d)
1493
1564
  append_keys(b, e, *keys)
1494
1565
  end
1495
1566
  if args
@@ -1512,7 +1583,7 @@ module Squared
1512
1583
  return cmd.to_s unless cmd.respond_to?(:done)
1513
1584
 
1514
1585
  raise_error 'no command added', hint: cmd.first unless cmd.size > 1
1515
- @session = nil if cmd.equal?(@session)
1586
+ @session = nil if cmd == @session
1516
1587
  cmd.done
1517
1588
  end
1518
1589
 
@@ -1522,33 +1593,69 @@ module Squared
1522
1593
  OptionPartition.arg?(target, *args, **kwargs)
1523
1594
  end
1524
1595
 
1525
- 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)
1526
1598
  return unless prefix
1527
1599
 
1528
- args.each do |val|
1529
- 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))
1530
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
1531
1610
  return block_given? ? yield(ret) : ret
1532
1611
  end
1533
1612
  nil
1534
1613
  end
1535
1614
 
1536
- def option_clear(opts, empty = true, target: @session, **kwargs)
1537
- 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))
1538
1622
 
1539
- OptionPartition.clear(target, opts, styles: theme[:inline], **kwargs)
1540
- opts.clear if empty
1541
- 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
1542
1638
  end
1543
1639
 
1544
1640
  def print_success
1545
- puts ARG[:SUCCESS]
1641
+ puts 'Success'
1546
1642
  end
1547
1643
 
1548
1644
  def print_error(*args, loglevel: Logger::WARN, **kwargs)
1549
1645
  return unless warning?
1550
1646
 
1551
- 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
1552
1659
  end
1553
1660
 
1554
1661
  def print_run(cmd, banner = true, verbose: nil, **)
@@ -1561,22 +1668,22 @@ module Squared
1561
1668
  def print_item(*val, series: false)
1562
1669
  puts unless printfirst?
1563
1670
  printsucc unless series
1564
- puts val unless val.empty? || (val.size == 1 && (!val.first || val.first.empty?))
1671
+ puts val unless val.empty? || (val.size == 1 && !val.first)
1565
1672
  end
1566
1673
 
1567
- def print_banner(*lines, client: false, styles: theme[:banner], border: borderstyle, **)
1674
+ def print_banner(*lines, client: false, styles: theme[:banner], border: borderstyle)
1568
1675
  pad = 0
1569
1676
  if styles
1570
1677
  if styles.any? { |s| s.to_s.end_with?('!') }
1571
1678
  pad = 1
1572
1679
  elsif !client && styles.size <= 1
1573
- styles = [:bold] + styles
1680
+ styles += [:bold]
1574
1681
  end
1575
1682
  end
1576
1683
  n = line_width lines
1577
1684
  ch = ' ' * pad
1578
1685
  index = -1
1579
- lines.map! do |val|
1686
+ ret = lines.map do |val|
1580
1687
  index += 1
1581
1688
  val = ch + val.ljust(n - (pad * 2)) + ch
1582
1689
  if styles && (pad == 1 || index == 0)
@@ -1585,23 +1692,24 @@ module Squared
1585
1692
  val
1586
1693
  end
1587
1694
  end
1588
- (lines << sub_style(ARG[:BORDER][1] * n, border)).join("\n")
1695
+ ret << sub_style(ARG[:BORDER][1] * n, border)
1696
+ ret.join("\n")
1589
1697
  end
1590
1698
 
1591
- 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)
1592
1700
  n = line_width lines
1593
1701
  sub = as_a sub
1594
- lines.map! do |val|
1702
+ ret = lines.map do |val|
1595
1703
  s = right ? val.rjust(n) : val.ljust(n)
1596
1704
  sub.each { |h| sub_style!(s, **h) }
1597
1705
  s
1598
1706
  end
1599
- [sub_style(ARG[:BORDER][1] * n, border)].concat(lines)
1600
- .tap { |ret| ret.reverse! if reverse }
1601
- .join("\n")
1707
+ ret.unshift(sub_style(ARG[:BORDER][1] * n, border))
1708
+ ret.reverse! if reverse
1709
+ ret.join("\n")
1602
1710
  end
1603
1711
 
1604
- def print_status(*args, from: nil, **kwargs)
1712
+ def print_status(*args, from: :completed, **kwargs)
1605
1713
  return if stdin? || silent?
1606
1714
 
1607
1715
  case from
@@ -1627,7 +1735,7 @@ module Squared
1627
1735
  workspace.format_desc([@desc, action, flag].compact, opts, **kwargs)
1628
1736
  end
1629
1737
 
1630
- def format_banner(cmd, banner: true, hint: nil, strip: nil)
1738
+ def format_banner(cmd, banner: true, hint: nil, strip: nil, quote: false)
1631
1739
  return unless banner && banner?
1632
1740
 
1633
1741
  if (data = workspace.banner_get(*@ref, group: group))
@@ -1635,7 +1743,7 @@ module Squared
1635
1743
 
1636
1744
  client = true
1637
1745
  else
1638
- data = Struct::BannerData.new(true, [:path], theme[:banner], theme[:border])
1746
+ data = Struct::BannerData.new(true, theme[:banner], theme[:border], [:path])
1639
1747
  end
1640
1748
  if verbose
1641
1749
  out = []
@@ -1648,6 +1756,7 @@ module Squared
1648
1756
  cmd = cmd.gsub(/(?:#{s = Regexp.escape(File.join(path, ''))}?(?=["'])|#{s})/, '')
1649
1757
  .gsub(/(?: -[^ ])? (?:""|'')/, '')
1650
1758
  end
1759
+ cmd.gsub!(/(?<= )(["'])([\w.-]+)\1(?= |\z)/, '\2') unless quote
1651
1760
  out << cmd.subhint(hint)
1652
1761
  end
1653
1762
  data.order.each do |val|
@@ -1663,7 +1772,7 @@ module Squared
1663
1772
  __send__ meth
1664
1773
  end
1665
1774
  end
1666
- val = val.reject { |item| !item || item == true }.join(s)
1775
+ val = val.compact.join(s)
1667
1776
  next unless found && !val.empty?
1668
1777
  elsif (val = __send__(val)).nil?
1669
1778
  next
@@ -1682,7 +1791,7 @@ module Squared
1682
1791
  unless items.empty?
1683
1792
  pad = items.size.to_s.size
1684
1793
  items.each_with_index do |val, i|
1685
- next unless matchany?(val.first, reg)
1794
+ next unless matchany?(reg, val.first)
1686
1795
 
1687
1796
  out << ('%*d. %s' % [pad, i.succ, block_given? ? yield(val) : val.first])
1688
1797
  end
@@ -1696,7 +1805,7 @@ module Squared
1696
1805
  out << ''
1697
1806
  end
1698
1807
  if from
1699
- out << (from = from.to_s)
1808
+ out << from
1700
1809
  /\A(#{Regexp.escape(from)})(.*)\z/
1701
1810
  end
1702
1811
  else
@@ -1721,7 +1830,7 @@ module Squared
1721
1830
  end
1722
1831
 
1723
1832
  def append_hash(data, target: @session || [], build: false)
1724
- if build && (type = env('BUILD', suffix: 'TYPE', ignore: false))
1833
+ if build && (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
1725
1834
  if (extra = data[type = "__#{type}__"] || data[type.to_sym]).is_a?(Hash)
1726
1835
  data = data.merge(extra)
1727
1836
  else
@@ -1729,7 +1838,8 @@ module Squared
1729
1838
  end
1730
1839
  end
1731
1840
  data.each do |key, val|
1732
- next if (key = key.to_s).start_with?('__')
1841
+ key = key.to_s
1842
+ next if key.start_with?('__')
1733
1843
 
1734
1844
  if val.nil? || extra || session_arg?(key, target: target)
1735
1845
  OptionPartition.delete_key(target, key)
@@ -1742,7 +1852,7 @@ module Squared
1742
1852
  append_repeat(key, val, target: target)
1743
1853
  when Numeric
1744
1854
  target << basic_option(key, val)
1745
- when FalseClass
1855
+ when false
1746
1856
  target << shell_option(key).sub(/^--(?!no-)/, '--no-')
1747
1857
  when Pathname
1748
1858
  target << shell_option(key, val, escape: false)
@@ -1779,9 +1889,7 @@ module Squared
1779
1889
 
1780
1890
  def append_first(*list, target: @session, flag: true, equals: false, escape: true, quote: true, force: true,
1781
1891
  **kwargs)
1782
- return if list.empty?
1783
-
1784
- list.flatten.each do |opt|
1892
+ list.each do |opt|
1785
1893
  next unless (val = option(opt, **kwargs))
1786
1894
 
1787
1895
  return target << if flag
@@ -1795,30 +1903,25 @@ module Squared
1795
1903
 
1796
1904
  def append_option(*list, target: @session, no: false, equals: false, escape: true, quote: true, force: true,
1797
1905
  **kwargs)
1798
- return if list.empty?
1799
-
1800
1906
  kwargs[:ignore] = false if no && !kwargs.key?(:ignore)
1801
- ret = []
1802
- list.flatten.each do |flag|
1907
+ ret = list.each_with_object([]) do |flag, out|
1803
1908
  next unless (val = option(flag, target: target, **kwargs))
1804
1909
 
1805
1910
  if no && val == '0'
1806
1911
  flag = "no-#{flag}"
1807
1912
  val = nil
1808
1913
  end
1809
- 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)
1810
1915
  end
1811
1916
  merge_list target, ret unless ret.empty?
1812
- ret
1813
1917
  end
1814
1918
 
1815
- def append_nocolor(target: @session)
1816
- 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)
1817
1921
  end
1818
1922
 
1819
1923
  def append_keys(base, data, *keys)
1820
- out = {}
1821
- keys.each do |key|
1924
+ ret = keys.each_with_object({}) do |key, out|
1822
1925
  next unless data.key?(key)
1823
1926
 
1824
1927
  out[key] = case (val = data[key])
@@ -1831,7 +1934,7 @@ module Squared
1831
1934
  end
1832
1935
  end
1833
1936
  base.update(data)
1834
- .update(out)
1937
+ .update(ret)
1835
1938
  end
1836
1939
 
1837
1940
  def merge_opts(base, data)
@@ -1874,22 +1977,9 @@ module Squared
1874
1977
  end
1875
1978
  end
1876
1979
 
1877
- def merge_list(base, data)
1878
- data = Array(data)
1879
- case base
1880
- when Array
1881
- base.concat(data)
1882
- when Set
1883
- base.merge(data)
1884
- else
1885
- Array(base).concat(data)
1886
- end
1887
- end
1888
-
1889
1980
  def collect_hash(data, pass: [])
1890
- ret = []
1891
- data.each { |key, val| ret.concat(val) unless pass.include?(key) }
1892
- ret
1981
+ data = data.reject { |key,| pass.include?(key) } unless pass.empty?
1982
+ data.values.flatten
1893
1983
  end
1894
1984
 
1895
1985
  def replace_bin(val)
@@ -1903,14 +1993,13 @@ module Squared
1903
1993
  ret = JSON.parse(val)
1904
1994
  raise_error 'invalid JSON'.subhint(kind.name), val, hint: hint if kind && !ret.is_a?(kind)
1905
1995
  ret
1906
- rescue StandardError => e
1907
- log&.warn e
1908
- print_error(e, subject: name)
1996
+ rescue => e
1997
+ print_error(kind ? Logger::ERROR : Logger::INFO, e, subject: name)
1909
1998
  end
1910
1999
 
1911
2000
  def parse_env(key)
1912
2001
  env(key) do |val|
1913
- 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) }
1914
2003
  end || []
1915
2004
  end
1916
2005
 
@@ -1928,77 +2017,53 @@ module Squared
1928
2017
  args
1929
2018
  end
1930
2019
 
1931
- def confirm_basic(msg, hint, default = 'Y', style: :inline, target: @session, prefix: target&.first, **kwargs)
1932
- return true if option('y', prefix: prefix)
1933
-
1934
- confirm("#{msg} [#{sub_style(hint.to_s, style.is_a?(Symbol) ? theme[style] : style)}]", default, **kwargs)
1935
- end
1936
-
1937
- def confirm_outdated(pkg, ver, type, cur = nil, lock: false, col0: 0, col1: 0, col2: nil, col3: 0, col4: 0,
1938
- **kwargs)
1939
- h = sub_style(semrev(type).upcase, (type == 1 && theme[:major]) || theme[:header])
1940
- case col0
1941
- when 0
1942
- col0 = "#{h}: "
1943
- when Numeric
1944
- puts h
1945
- col0 = ' ' * col0
1946
- else
1947
- puts h
1948
- end
1949
- b = sub_style pkg.ljust(col1), theme[:inline]
1950
- cur ||= 'locked' if lock
1951
- c = if cur
1952
- cur = cur.ljust(col2 || cur.size.succ)
1953
- lock ? sub_style(cur, color(:red)) : cur
1954
- end
1955
- d = type == 1 || lock ? 'N' : 'Y'
1956
- e = "#{col0}#{b}#{c}#{sub_style(col1 > 0 ? ver.ljust(col3) : ver.rjust(ver.size.succ), theme[:inline])}"
1957
- 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)
1958
2022
  end
1959
2023
 
1960
2024
  def confirm_semver(msg, type, style: (type == 1 && theme[:major]) || :inline, timeout: 0, **kwargs)
1961
- 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)
1962
2034
  end
1963
2035
 
1964
2036
  def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil, multiple: false,
1965
2037
  force: true, **kwargs)
1966
2038
  puts unless series || printfirst?
1967
- ret = choice(msg, list, multiple: multiple, force: force, **kwargs).tap do |val|
1968
- next unless !val || val.empty?
1969
-
2039
+ ret = choice(msg, list, multiple: multiple, force: force, **kwargs)
2040
+ unless ret && !ret.empty?
1970
2041
  exit 1 if force
1971
- return nil
2042
+ return
1972
2043
  end
1973
- 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
1974
2045
  if column
1975
2046
  a, b = Array(column)
1976
- ret = Array(ret).map! { |val| val[a, b || 1] }
2047
+ ret = Array(ret).map { |val| val[a, b || 1] }
1977
2048
  ret = ret.first unless multiple
1978
2049
  end
1979
2050
  if accept
1980
2051
  hint = Array(ret).map { |val| sub_style(val.to_s, theme[:inline]) }.join(', ')
1981
2052
  accept = Array(accept).map { |val| Array(val) }
1982
- if accept.any? { |val| val[1] == true }
1983
- ret = [ret]
1984
- multiple = -1
1985
- end
1986
- loop do
1987
- item = accept.first
2053
+ ret = Array(ret) if accept.any? { |val| val[1] == true }
2054
+ begin
2055
+ item = accept.shift
1988
2056
  c = confirm("#{item[0]}#{" [#{hint}]" if hint}", item[2] ? 'Y' : 'N')
1989
2057
  if item[1] == true
1990
2058
  ret << c
1991
2059
  elsif !c
1992
- break
2060
+ exit 1
1993
2061
  end
1994
2062
  hint = nil
1995
- accept.shift
1996
- break if accept.empty?
1997
- end
1998
- exit 1 unless accept.empty?
2063
+ end until accept.empty?
1999
2064
  end
2000
2065
  if values
2001
- ret = [ret] unless accept && multiple == -1
2066
+ ret = Array(ret)
2002
2067
  Array(values).each do |val|
2003
2068
  if val.is_a?(Array)
2004
2069
  val, force = val
@@ -2006,7 +2071,7 @@ module Squared
2006
2071
  force = false
2007
2072
  end
2008
2073
  val = readline(val, force: force)
2009
- ret << (val unless val.empty?)
2074
+ ret << (val.empty? ? nil : val)
2010
2075
  end
2011
2076
  end
2012
2077
  printsucc unless series
@@ -2024,10 +2089,11 @@ module Squared
2024
2089
  def command_args(args, min: 0, force: false, **kwargs)
2025
2090
  return if args.size > min || option('i', 'interactive', **kwargs, equals: '0')
2026
2091
 
2027
- readline('Enter arguments', force: force)
2092
+ ret = readline('Enter arguments', force: force)
2093
+ args << ret unless ret.empty?
2028
2094
  end
2029
2095
 
2030
- def block_args(fallback = nil, &blk)
2096
+ def block_args(fallback = [], &blk)
2031
2097
  return fallback if (ret = instance_eval(&blk)).nil?
2032
2098
 
2033
2099
  Array(ret)
@@ -2046,49 +2112,40 @@ module Squared
2046
2112
  if workspace.powershell?
2047
2113
  "#{shell_bin('powershell.exe')} -Command \"& {#{args.join(' ; ')}}\"#{out}"
2048
2114
  else
2049
- args.map! { |val| "#{val}#{out}" }.join(' && ')
2115
+ args.map { |val| "#{val}#{out}" }.join(' && ')
2050
2116
  end
2051
2117
  end
2052
2118
 
2053
2119
  def relativepath(*list, all: false)
2054
- list.flatten.map! { |val| Pathname.new(val) }.select { |val| projectpath?(val) }.map! do |val|
2055
- ret = (val.absolute? ? val.relative_path_from(path) : val.cleanpath).to_s
2056
- all && val.to_s.end_with?('/') ? "#{ret}/*" : ret
2057
- 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
2058
2126
  end
2059
2127
 
2060
- def projectmap(files, parent: false, pass: true)
2128
+ def projectmap(files, resolve: true, parent: false, pass: true)
2061
2129
  unless parent
2062
- proj = files.select { |val| projectpath?(val) }
2063
- raise_error 'pathspec not within worktree' unless pass || files.size == proj.size
2064
- files = proj
2065
- end
2066
- files.map { |val| val == '.' ? '.' : shell_quote(basepath(val)) }
2067
- end
2068
-
2069
- def matchmap(list, prefix = nil, negate: false)
2070
- ret = negate ? [[], []] : []
2071
- list.each do |val|
2072
- unless val.is_a?(Regexp)
2073
- val = val.dup
2074
- n = 1 if negate && val.delete_prefix!('!')
2075
- if prefix
2076
- a = nil
2077
- val.sub!(/\A(\^|\\A)/) do |s|
2078
- a = s
2079
- nil
2080
- end
2081
- val = ".*#{val}" unless a
2082
- end
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'
2083
2134
  end
2084
- pat = Regexp.new("#{a}#{prefix}#{val == '*' ? '.+' : val}")
2085
- if negate
2086
- ret[n || 0] << pat
2135
+ end
2136
+ files.map do |val|
2137
+ if val == '.'
2138
+ '.'
2087
2139
  else
2088
- ret << pat
2140
+ val = basepath(val)
2141
+ val = val.relative_path_from(path) unless resolve
2142
+ shell_quote val
2089
2143
  end
2090
2144
  end
2091
- ret
2145
+ end
2146
+
2147
+ def symjoin(*args, char: ':')
2148
+ args.join(char).to_sym
2092
2149
  end
2093
2150
 
2094
2151
  def semver(val)
@@ -2113,11 +2170,11 @@ module Squared
2113
2170
  return -1 if (b = other.scan(SEM_VER)).empty?
2114
2171
  return 1 if (a = val.scan(SEM_VER)).empty?
2115
2172
 
2116
- a, b = [a.first, b.first].map! do |c|
2173
+ a, b = [a.first, b.first].map do |c|
2117
2174
  d = begin
2118
- Integer(c[6]).to_s
2119
- rescue StandardError
2120
- c[6] ? '-1' : '0'
2175
+ Integer(c[5]).to_s
2176
+ rescue
2177
+ c[5] ? '-1' : '0'
2121
2178
  end
2122
2179
  [c[0], c[2], c[4] || '0', d]
2123
2180
  end
@@ -2154,24 +2211,13 @@ module Squared
2154
2211
  end
2155
2212
 
2156
2213
  def semtype(cur, lat)
2157
- if semmajor?(cur, lat)
2158
- 1
2159
- else
2160
- cur[2] == lat[2] ? 3 : 2
2161
- end
2214
+ return 1 if semmajor?(cur, lat)
2215
+
2216
+ cur[2] == lat[2] ? 3 : 2
2162
2217
  end
2163
2218
 
2164
- def semrev(type)
2165
- case type
2166
- when 1
2167
- 'major'
2168
- when 2
2169
- 'minor'
2170
- when 3
2171
- 'patch'
2172
- else
2173
- 'unknown'
2174
- end
2219
+ def semmajor?(cur, want)
2220
+ (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
2175
2221
  end
2176
2222
 
2177
2223
  def semgte?(val, other = nil)
@@ -2197,7 +2243,7 @@ module Squared
2197
2243
  def shortname(*args, suffix: '?', delim: ',', pass: false)
2198
2244
  return unless TASK_METADATA || pass
2199
2245
 
2200
- args.map! do |ch|
2246
+ args.map do |ch|
2201
2247
  "#{ch}/#{case ch
2202
2248
  when 'i'
2203
2249
  'nteractive'
@@ -2223,7 +2269,7 @@ module Squared
2223
2269
  end
2224
2270
 
2225
2271
  def color(val)
2226
- (ret = theme[val]) && !ret.empty? ? ret : [val]
2272
+ (ret = Array(theme[val])).empty? ? [val] : ret
2227
2273
  end
2228
2274
 
2229
2275
  def colormap(val)
@@ -2232,7 +2278,7 @@ module Squared
2232
2278
 
2233
2279
  def verbosetype
2234
2280
  case verbose
2235
- when TrueClass
2281
+ when true
2236
2282
  1
2237
2283
  when Numeric
2238
2284
  verbose.succ
@@ -2242,9 +2288,15 @@ module Squared
2242
2288
  end
2243
2289
 
2244
2290
  def on(event, from, *args, **kwargs)
2245
- return unless from && @events.key?(event)
2291
+ return unless from && @on[:event].key?(event)
2246
2292
 
2247
- 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|
2248
2300
  target, opts = if obj.is_a?(Array) && obj[1].is_a?(Hash)
2249
2301
  [obj[0], kwargs.empty? ? obj[1] : obj[1].merge(kwargs)]
2250
2302
  else
@@ -2261,35 +2313,58 @@ module Squared
2261
2313
  end
2262
2314
  end
2263
2315
 
2264
- 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)
2265
2317
  log&.error err
2266
- unless dryrun
2267
- ret = on :error, from, err
2268
- 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
2269
2329
  end
2270
2330
  print_error(err, pass: pass) unless ret
2271
2331
  end
2272
2332
 
2273
- def pwd_set(pass: false, exception: self.exception, dryrun: false, from: nil)
2274
- 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
2275
2335
 
2276
- pwd = Dir.pwd
2277
- Dir.chdir(path)
2278
- yield
2279
- rescue StandardError => e
2280
- 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)
2281
2356
  ensure
2282
2357
  Dir.chdir(pwd) if pwd
2283
2358
  end
2284
2359
 
2285
- def run_set(cmd, val = nil, opts: nil, global: false, **)
2360
+ def run_set(cmd, val = nil, opts: nil, global: nil, **)
2286
2361
  noopt = @output[1] == false && !@output[0].nil?
2287
2362
  noenv = @output[2] == false
2288
2363
  parse = lambda do |data|
2289
2364
  ret = []
2290
2365
  if data[:command]
2291
2366
  ret[0] = data[:command]
2292
- ret[1] = data[:opts] unless noopt
2367
+ ret[1] = data[:opts] unless diso
2293
2368
  ret[3] = data[:args]
2294
2369
  elsif data[:script]
2295
2370
  ret[1] = data[:script]
@@ -2298,15 +2373,22 @@ module Squared
2298
2373
  else
2299
2374
  ret[0] = false
2300
2375
  end
2301
- ret[2] = data[:env] unless noenv
2376
+ ret[2] = data[:env] unless dise
2302
2377
  ret
2303
2378
  end
2304
- 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
2305
2387
  case cmd
2306
2388
  when Hash
2307
2389
  @output = parse.call(data)
2308
2390
  when Enumerable
2309
- @output = if cmd.all?(Hash)
2391
+ @output = if cmd.all? { |data| data.is_a?(Hash) }
2310
2392
  noopt = false
2311
2393
  noenv = false
2312
2394
  cmd.map { |data| parse.call(data) }
@@ -2333,12 +2415,19 @@ module Squared
2333
2415
  end
2334
2416
  end
2335
2417
 
2336
- def script_set(cmd, prod: nil, args: nil, global: false, **)
2418
+ def script_set(cmd, prod: nil, args: nil, global: nil, **)
2337
2419
  return if @output[1] == false && @output[0].nil?
2338
2420
 
2339
- 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
2340
2429
  @output[0] = nil
2341
- @output[1] = if self.global && cmd.is_a?(Array)
2430
+ @output[1] = if global && cmd.is_a?(Array)
2342
2431
  cmd[prod == true ? 1 : 0]
2343
2432
  else
2344
2433
  cmd
@@ -2360,6 +2449,15 @@ module Squared
2360
2449
  end
2361
2450
  end
2362
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
+
2363
2461
  def pass_set(val)
2364
2462
  @pass = Array(val).freeze
2365
2463
  end
@@ -2398,23 +2496,17 @@ module Squared
2398
2496
  end
2399
2497
  end
2400
2498
 
2401
- def dependfile_set(list, default: 0)
2499
+ def dependfile_set(list)
2402
2500
  @dependindex = if @dependname
2403
2501
  @dependfile = basepath @dependname
2404
2502
  list.index(@dependname)
2405
2503
  else
2406
- list.index { |file| exist?(file) }.tap { |i| @dependfile = basepath(list[i || default]) }
2407
- end || (list unless enabled?)
2408
- end
2409
-
2410
- def dependindex
2411
- dependfile_set @dependindex if @dependindex.is_a?(Array)
2412
-
2413
- @dependindex unless @dependindex.is_a?(Array)
2504
+ list.index { |file| exist?(file) }.tap { |i: 0| @dependfile = basepath(list[i]) }
2505
+ end
2414
2506
  end
2415
2507
 
2416
- def as_get(val, from)
2417
- (global && @as[from][val]) || val
2508
+ def as_get(val, from, *type)
2509
+ (global(*type) && @as[from][val]) || val
2418
2510
  end
2419
2511
 
2420
2512
  def unpack_get(*)
@@ -2437,7 +2529,7 @@ module Squared
2437
2529
  next if (items = children.select { |item| item.task_include?(key) }).empty?
2438
2530
 
2439
2531
  ws.task_desc(@desc, action, 'workspace')
2440
- 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) }
2441
2533
  end
2442
2534
  end
2443
2535
  end
@@ -2446,10 +2538,6 @@ module Squared
2446
2538
  @only ? !@only.include?(key) : @pass.include?(key)
2447
2539
  end
2448
2540
 
2449
- def matchany?(val, list, empty: true)
2450
- list.empty? ? empty : list.any? { |pat| val.match?(pat) }
2451
- end
2452
-
2453
2541
  def projectpath?(val)
2454
2542
  ret = Pathname.new(val).cleanpath
2455
2543
  ret.absolute? ? ret.to_s.start_with?(File.join(path, '')) : !ret.to_s.start_with?(File.join('..', ''))
@@ -2462,10 +2550,6 @@ module Squared
2462
2550
  false
2463
2551
  end
2464
2552
 
2465
- def semmajor?(cur, want)
2466
- (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[6]
2467
- end
2468
-
2469
2553
  def printfirst?
2470
2554
  @@print_order == 0
2471
2555
  end
@@ -2507,30 +2591,24 @@ module Squared
2507
2591
  end
2508
2592
 
2509
2593
  def success?(run, *cond)
2510
- case run
2511
- when TrueClass
2512
- true
2513
- when FalseClass
2514
- false
2515
- else
2516
- $?&.success?
2517
- end.tap do |ret|
2518
- next unless cond.none? { |val| val == false }
2519
-
2594
+ ret = case run
2595
+ when true, false
2596
+ run
2597
+ else
2598
+ $?.success?
2599
+ end
2600
+ if cond.none? { |val| val == false }
2520
2601
  if block_given?
2521
2602
  yield ret
2522
2603
  elsif ret && stdout? && banner?
2523
2604
  print_success
2524
2605
  end
2525
2606
  end
2607
+ ret
2526
2608
  end
2527
2609
 
2528
2610
  def banner?
2529
- ARG[:BANNER] && !env('BANNER', equals: '0')
2530
- end
2531
-
2532
- def dryrun?(*, target: @session, prefix: target&.first)
2533
- Array(target).include?('--dry-run') || !option('dry-run', target: target, prefix: prefix).nil?
2611
+ ARG[:BANNER] && env('BANNER', notequals: '0')
2534
2612
  end
2535
2613
 
2536
2614
  def pwd?
@@ -2542,7 +2620,7 @@ module Squared
2542
2620
  end
2543
2621
 
2544
2622
  def stdout?
2545
- !silent? && !stdin?
2623
+ !!verbose && !stdin?
2546
2624
  end
2547
2625
 
2548
2626
  def verbose?
@@ -2557,29 +2635,9 @@ module Squared
2557
2635
  workspace.warning
2558
2636
  end
2559
2637
 
2560
- def has_value?(target, *args)
2561
- return false unless target.is_a?(Enumerable)
2562
-
2563
- args = args.first if args.size == 1 && args.first.is_a?(Enumerable)
2564
- args.any? { |obj,| target.include?(obj) }
2565
- end
2566
-
2567
- def has_value!(target, *args, first: false)
2568
- return unless target.is_a?(Enumerable)
2569
-
2570
- args = args.first if args.size == 1 && args.first.is_a?(Enumerable)
2571
- found = false
2572
- args.each do |val|
2573
- if target.respond_to?(:delete?)
2574
- found = true if target.delete?(val)
2575
- elsif target.respond_to?(:delete)
2576
- found = true if target.delete(val)
2577
- elsif target.include?(val)
2578
- found = true
2579
- end
2580
- break if found && first
2581
- end
2582
- 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)
2583
2641
  end
2584
2642
 
2585
2643
  def variables
@@ -2599,12 +2657,13 @@ module Squared
2599
2657
  end
2600
2658
 
2601
2659
  def scriptargs
2602
- { 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) }
2603
2662
  end
2604
2663
  end
2605
2664
 
2606
- Application.implement(Base, base: true)
2607
- 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])
2608
2667
  end
2609
2668
  end
2610
2669
  end