squared 0.3.14 → 0.4.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.
@@ -15,8 +15,10 @@ module Squared
15
15
  include Rake::DSL
16
16
 
17
17
  VAR_SET = %i[parent global envname dependfile theme run script env pass].freeze
18
- SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+))?[-.]?(\S+)?\b/.freeze
19
- private_constant :VAR_SET, :SEM_VER
18
+ BLK_SET = %i[run depend doc lint test copy clean].freeze
19
+ SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?\b/.freeze
20
+ URI_SCHEME = %r{^([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
21
+ private_constant :VAR_SET, :BLK_SET, :SEM_VER, :URI_SCHEME
20
22
 
21
23
  class << self
22
24
  def populate(*); end
@@ -25,7 +27,7 @@ module Squared
25
27
  def bannerargs(*); end
26
28
 
27
29
  def tasks
28
- %i[build depend graph doc lint test copy clean].freeze
30
+ (%i[build archive graph] + BLK_SET).freeze
29
31
  end
30
32
 
31
33
  def as_path(val)
@@ -51,7 +53,8 @@ module Squared
51
53
  end
52
54
 
53
55
  (@@tasks = {})[ref] = {
54
- 'graph' => %i[run print].freeze
56
+ 'graph' => %i[run print].freeze,
57
+ 'unpack' => %i[zip tar tgz tar.gz txz tar.xz].freeze
55
58
  }.freeze
56
59
  @@task_desc = Rake::TaskManager.record_task_metadata
57
60
  @@print_order = 0
@@ -59,8 +62,8 @@ module Squared
59
62
  attr_reader :name, :project, :workspace, :path, :theme, :exception, :pipe, :verbose,
60
63
  :group, :parent, :dependfile
61
64
 
62
- def initialize(workspace, path, name, *, group: nil, graph: nil, pass: nil, exclude: nil, verbose: nil,
63
- first: {}, last: {}, error: {}, common: ARG[:COMMON], **kwargs)
65
+ def initialize(workspace, path, name, *, group: nil, graph: nil, pass: nil, exclude: nil, release: nil,
66
+ archive: nil, first: {}, last: {}, error: {}, common: ARG[:COMMON], **kwargs)
64
67
  @path = path
65
68
  @workspace = workspace
66
69
  @name = name.to_s.freeze
@@ -73,6 +76,13 @@ module Squared
73
76
  @copy = kwargs[:copy]
74
77
  @clean = kwargs[:clean]
75
78
  @version = kwargs[:version]
79
+ @archive = case archive
80
+ when String, Array
81
+ { uri: archive }
82
+ when Hash
83
+ archive
84
+ end
85
+ @release = release
76
86
  @envname = @name.gsub(/[^\w]+/, '_').upcase.freeze
77
87
  @exception = env_bool(kwargs[:exception], workspace.exception, strict: true)
78
88
  @pipe = env_pipe(kwargs[:pipe], workspace.pipe, strict: true)
@@ -80,7 +90,7 @@ module Squared
80
90
  when nil
81
91
  workspace.verbose
82
92
  when String
83
- env_bool(verbose, workspace.verbose, strict: true, index: true)
93
+ env_pipe(verbose, workspace.verbose, strict: true, index: true)
84
94
  else
85
95
  verbose
86
96
  end
@@ -108,7 +118,7 @@ module Squared
108
118
  @parent = nil
109
119
  @global = false
110
120
  run_set(kwargs[:run], kwargs[:env], opts: kwargs.fetch(:opts, true))
111
- initialize_ref(Base.ref)
121
+ initialize_ref Base.ref
112
122
  end
113
123
 
114
124
  def initialize_ref(ref)
@@ -116,7 +126,7 @@ module Squared
116
126
  end
117
127
 
118
128
  def initialize_build(ref, **kwargs)
119
- initialize_ref(ref)
129
+ initialize_ref ref
120
130
  if (@script = @workspace.script_get(group: @group, ref: ref))
121
131
  if @script[:log] && !kwargs.key?(:log)
122
132
  kwargs[:log] = @script[:log]
@@ -166,43 +176,40 @@ module Squared
166
176
  def initialize_events(ref, **)
167
177
  return unless (events = @workspace.events_get(group: @group, ref: ref))
168
178
 
169
- events.each { |task, data| data.each { |ev, blk| (@events[ev] ||= {})[task] ||= blk } }
179
+ events.each do |task, data|
180
+ data.each { |ev, blk| (@events[ev] ||= {})[task] ||= [blk] }
181
+ end
170
182
  end
171
183
 
172
184
  def initialize_logger(log: nil, **)
173
185
  return if @log
174
186
 
175
187
  log = log.is_a?(Hash) ? log.dup : { file: log }
176
- if (file = env('LOG_FILE'))
177
- file = DateTime.now.strftime(file)
178
- elsif (val = env('LOG_AUTO'))
179
- file = "#{@name}-%s.log" % [case val
180
- when 'y', 'year'
181
- Date.today.year
182
- when 'm', 'month'
183
- Date.today.strftime('%Y-%m')
184
- when 'd', 'day', '1'
185
- Date.today
186
- else
187
- val.include?('%') ? DateTime.now.strftime(val) : DateTime.now.strftime
188
- end]
189
- elsif (val = log[:file])
190
- file = val.is_a?(String) ? DateTime.now.strftime(val) : "#{@name}-#{Date.today}.log"
191
- end
192
- if file
193
- file = (val = env('LOG_DIR')) ? @workspace.home.join(val, file) : @workspace.home.join(file)
188
+ unless (file = env('LOG_FILE'))
189
+ file = case env('LOG_AUTO')
190
+ when 'y', 'year'
191
+ "#{@name}-#{Date.today.year}.log"
192
+ when 'm', 'month'
193
+ "#{@name}-#{Date.today.strftime('%Y-%m')}.log"
194
+ when 'd', 'day', '1'
195
+ "#{@name}-#{Date.today}.log"
196
+ end
197
+ end
198
+ if file ||= log[:file]
199
+ file = Date.today.strftime(file)
200
+ file = (dir = env('LOG_DIR')) ? @workspace.home.join(dir, file) : @workspace.home.join(file)
194
201
  begin
195
202
  file = file.realdirpath
196
203
  rescue StandardError => e
197
204
  raise if @exception
198
205
 
199
206
  file = nil
200
- warn log_message(Logger::WARN, e) if warning?
207
+ warn log_message(Logger::WARN, e, pass: true) if warning?
201
208
  end
202
209
  end
203
210
  log[:progname] ||= @name
204
211
  if (val = env('LOG_LEVEL', ignore: false))
205
- log[:level] = val
212
+ log[:level] = val.match?(/^\d$/) ? log_sym(val.to_i) : val
206
213
  end
207
214
  log.delete(:file)
208
215
  @log = [file, log]
@@ -211,20 +218,18 @@ module Squared
211
218
  def initialize_env(dev: nil, prod: nil, **)
212
219
  @dev = env_match('BUILD', dev, suffix: 'DEV', strict: true)
213
220
  @prod = env_match('BUILD', prod, suffix: 'PROD', strict: true)
214
- cmd = @output[0]
215
- if @output[2] != false && (val = env('BUILD', suffix: 'ENV'))
216
- begin
217
- data = JSON.parse(val)
218
- raise_error('invalid JSON object', val, hint: "BUILD_#{@envname}_ENV") unless data.is_a?(Hash)
219
- rescue StandardError => e
220
- log&.warn e
221
- else
222
- @output[2] = data
221
+ unless @output[2] == false || !(val = env('BUILD', suffix: 'ENV'))
222
+ data = parse_json(val, hint: "BUILD_#{@envname}_ENV")
223
+ @output[2] = data if data
224
+ end
225
+ unless @output[0] == false || @output[0].is_a?(Array)
226
+ if (val = env('BUILD', suffix: 'OPTS'))
227
+ n = @output[0] ? 1 : 3
228
+ @output[n] = merge_opts(@output[n], shell_split(val, escape: false))
229
+ end
230
+ if (val = env(ref.to_s.upcase, suffix: 'OPTS'))
231
+ @output[4] = merge_opts(@output[4], shell_split(val, escape: false))
223
232
  end
224
- end
225
- if cmd != false && !cmd.is_a?(Array)
226
- @output[cmd ? 1 : 3] = shell_split(val, escape: false, join: true) if (val = env('BUILD', suffix: 'OPTS'))
227
- @output[4] = shell_split(val, escape: false, join: true) if !cmd && (val = env('SCRIPT', suffix: 'OPTS'))
228
233
  end
229
234
  @version = val if (val = env('BUILD', suffix: 'VERSION'))
230
235
  return unless (val = env('BUILD', strict: true))
@@ -255,18 +260,18 @@ module Squared
255
260
  flags.each do |flag|
256
261
  case action
257
262
  when 'graph'
258
- break unless graph?
263
+ next unless graph?
259
264
 
260
265
  format_desc action, flag, '(-)project*'
261
266
  task flag do |_, args|
262
- args = args.to_a.reject { |val| name == val.to_s }
267
+ args = param_guard(action, flag, args: args.to_a.reject { |val| name == val.to_s })
263
268
  if flag == :run
264
269
  graph args
265
270
  else
266
271
  out, done = graph(args, out: [])
267
272
  out.map! do |val|
268
273
  done.each_with_index do |proj, i|
269
- next unless val =~ / #{Regexp.escape(proj.name)}(?:@\d|\z)/
274
+ next unless val.match?(/ #{Regexp.escape(proj.name)}(?:@\d|\z)/)
270
275
 
271
276
  val += " (#{i.succ})"
272
277
  break
@@ -280,6 +285,25 @@ module Squared
280
285
  ])
281
286
  end
282
287
  end
288
+ when 'unpack'
289
+ format_desc action, flag, 'tag/url,dir,digest?,f/force?'
290
+ task flag, [:tag, :dir, :digest, :force] do |_, args|
291
+ tag = param_guard(action, flag, args: args, key: :tag)
292
+ dir = param_guard(action, flag, args: args, key: :dir)
293
+ unless tag.match?(URI_SCHEME)
294
+ raise_error 'no base uri' unless @release
295
+
296
+ tag = "#{@release.include?('??') ? @release.sub('??', tag) : @release + tag}.#{flag}"
297
+ end
298
+ case (digest = args.digest)
299
+ when 'f', 'force'
300
+ digest = nil
301
+ force = true
302
+ else
303
+ force = args.fetch(:force, false)
304
+ end
305
+ unpack(basepath(dir), uri: tag, digest: digest, ext: flag.to_s, force: force)
306
+ end
283
307
  end
284
308
  end
285
309
  end
@@ -348,18 +372,19 @@ module Squared
348
372
 
349
373
  out = obj.link(self, *args, **kwargs, &blk) if obj.respond_to?(:link)
350
374
  if !out
351
- warn log_message(Logger::WARN, 'link not compatible', subject: obj.to_s, hint: name)
375
+ warn log_message(Logger::WARN, 'link not compatible', subject: obj.to_s, hint: name, pass: true)
352
376
  elsif out.respond_to?(:build)
353
377
  out.build
354
378
  end
355
379
  self
356
380
  end
357
381
 
358
- def build(*args, sync: invoked_sync?('build'), from: :run, **)
382
+ def build(*args, sync: invoked_sync?('build'), from: @buildtype || :build, **)
359
383
  banner = verbose
360
384
  if args.empty?
361
- return unless from == :run
385
+ return unless from == (@buildtype || :build)
362
386
 
387
+ run_b(@run, sync: sync, from: from) if series?(@run)
363
388
  args = @output
364
389
  banner = verbose == 1 if task_invoked?('build', 'build:sync')
365
390
  end
@@ -370,7 +395,7 @@ module Squared
370
395
  a, b, c, d, e = val
371
396
  case b
372
397
  when Hash
373
- b = append_hash(b).join(' ')
398
+ b = append_hash(b, build: true).join(' ')
374
399
  when Enumerable
375
400
  b = b.to_a.join(' ')
376
401
  end
@@ -386,7 +411,7 @@ module Squared
386
411
  end
387
412
  cmd = cmd.join(' && ')
388
413
  else
389
- cmd, opts, var, flags, scr = args
414
+ cmd, opts, var, flags, extra = args
390
415
  end
391
416
  if cmd
392
417
  cmd = as_get(cmd)
@@ -394,7 +419,7 @@ module Squared
394
419
  flags = append_hash(flags).join(' ') if flags.is_a?(Hash)
395
420
  case opts
396
421
  when Hash
397
- opts = append_hash(opts)
422
+ opts = append_hash(opts, build: true)
398
423
  cmd = as_a(cmd).push(flags).concat(opts).compact.join(' ')
399
424
  when Enumerable
400
425
  cmd = as_a(cmd).concat(opts.to_a)
@@ -404,10 +429,9 @@ module Squared
404
429
  cmd = [cmd, flags, opts].compact.join(' ') if opts || flags
405
430
  end
406
431
  else
407
- return unless respond_to?(:compose)
432
+ return unless (opts || extra) && respond_to?(:compose)
408
433
 
409
- cmd = compose(as_get(opts), flags, script: true, args: scr, from: from)
410
- from = :script if from == :run && script?
434
+ cmd = compose(as_get(opts), flags, script: true, args: extra, from: from)
411
435
  end
412
436
  run(cmd, var, from: from, banner: banner, sync: sync)
413
437
  end
@@ -416,6 +440,12 @@ module Squared
416
440
  run_b(@depend, sync: sync, from: :depend)
417
441
  end
418
442
 
443
+ def archive(*, sync: invoked_sync?('archive'), **)
444
+ return unless @archive.is_a?(Array)
445
+
446
+ unpack(path, **@archive, sync: sync, from: :archive)
447
+ end
448
+
419
449
  def doc(*, sync: invoked_sync?('doc'), **)
420
450
  run_b(@doc, sync: sync, from: :doc)
421
451
  end
@@ -445,27 +475,31 @@ module Squared
445
475
  rescue StandardError => e
446
476
  log&.error e
447
477
  ret = on(:error, from, e)
448
- raise if @exception && ret != true
478
+ raise if exception && ret != true
449
479
  end
450
- when Enumerable
451
- as_a(@clean).each do |val|
452
- val = val.to_s
453
- path = basepath(val)
454
- if path.directory? && val =~ %r{[\\/]\z}
455
- log&.warn "rm -rf #{path}"
456
- FileUtils.rm_rf(path, verbose: verbose)
457
- else
458
- log&.warn "rm #{path}"
459
- (val.include?('*') ? Dir[path] : [path]).each do |file|
460
- next unless File.file?(file)
461
-
462
- begin
463
- File.delete(file)
464
- rescue StandardError => e
465
- log&.error e
480
+ else
481
+ if @clean.is_a?(Enumerable) && !series?(@clean)
482
+ as_a(@clean).each do |val|
483
+ val = val.to_s
484
+ path = basepath(val)
485
+ if path.directory? && val.match?(%r{[\\/]$})
486
+ log&.warn "rm -rf #{path}"
487
+ path.rmtree(verbose: verbose)
488
+ else
489
+ log&.warn "rm #{path}"
490
+ (val.include?('*') ? Dir[path] : [path]).each do |file|
491
+ next unless File.file?(file)
492
+
493
+ begin
494
+ File.delete(file)
495
+ rescue StandardError => e
496
+ log&.error e
497
+ end
466
498
  end
467
499
  end
468
500
  end
501
+ else
502
+ run_b(@clean, sync: sync)
469
503
  end
470
504
  end
471
505
  on :last, :clean
@@ -502,6 +536,119 @@ module Squared
502
536
  end
503
537
  end
504
538
 
539
+ def unpack(target, sync: true, uri: nil, digest: nil, ext: nil, force: false, depth: 1, headers: {},
540
+ from: :unpack)
541
+ if !target.exist?
542
+ target.mkpath
543
+ elsif !target.directory?
544
+ raise_error('invalid location', hint: target)
545
+ elsif !target.empty?
546
+ raise_error('directory not empty', hint: target) unless force || env('UNPACK_FORCE')
547
+ create = true
548
+ end
549
+ if digest
550
+ if (n = digest.index(':').to_i) > 0
551
+ size = digest[0, n].downcase
552
+ digest = digest[n + 1..-1]
553
+ else
554
+ size = digest.size
555
+ end
556
+ algo = case size
557
+ when 32, 'md5'
558
+ Digest::MD5
559
+ when 'rmd160'
560
+ Digest::RMD160
561
+ when 40, 'sha1'
562
+ Digest::SHA1
563
+ when 64, 'sha256'
564
+ Digest::SHA256
565
+ when 96, 'sha384'
566
+ Digest::SHA384
567
+ when 128, 'sha512'
568
+ Digest::SHA512
569
+ else
570
+ raise_error("invalid checksum: #{digest}", hint: name)
571
+ end
572
+ end
573
+ if (val = env('HEADERS')) && (out = parse_json(val, hint: "HEADERS_#{@envname}"))
574
+ headers = out
575
+ end
576
+ require 'open-uri'
577
+ data = nil
578
+ (uri = as_a(uri)).each_with_index do |url, index|
579
+ last = index == uri.size - 1
580
+ URI.open(url, headers) do |f|
581
+ data = f.read
582
+ if algo && algo.hexdigest(data) != digest
583
+ data = nil
584
+ raise_error("checksum failed: #{digest}", hint: url) if last
585
+ end
586
+ next if ext && index == 0
587
+
588
+ case f.content_type
589
+ when 'application/zip'
590
+ ext = 'zip'
591
+ when 'application/x-gzip'
592
+ ext = 'tgz'
593
+ when 'application/x-xz'
594
+ ext = 'txz'
595
+ end
596
+ end
597
+ if data
598
+ uri = url
599
+ break
600
+ elsif last
601
+ raise_error('no content', hint: url)
602
+ end
603
+ end
604
+ ext ||= URI.parse(uri).path[/^.+?\.((?:tar\.)?\w+)$/i, 1]
605
+ if (n = env("#{ext == 'zip' ? 'ZIP' : 'TAR'}_DEPTH", ignore: false))
606
+ depth = n.to_i
607
+ end
608
+ begin
609
+ require 'tempfile'
610
+ file = Tempfile.new("#{name}-")
611
+ file.write(data)
612
+ file.close
613
+ if create
614
+ warn log_message(Logger::WARN, name, 'force remove', hint: target, pass: true)
615
+ target.rmtree(verbose: true)
616
+ target.mkpath
617
+ end
618
+ case ext
619
+ when 'zip', 'aar'
620
+ session 'unzip', shell_quote(file.path), quote_option('d', target)
621
+ when 'tar', 'tgz', 'tar.gz', 'tar.xz'
622
+ flags = +(verbose ? 'v' : '')
623
+ case ext
624
+ when 'tgz', 'tar.gz'
625
+ flags += 'z'
626
+ when 'txz', 'tar.xz'
627
+ flags += 'J'
628
+ end
629
+ session 'tar', "-x#{flags}", basic_option('strip-components', depth), quote_option('f', file.path),
630
+ quote_option('C', target)
631
+ depth = 0
632
+ else
633
+ raise_error("unsupported format: #{ext}", hint: uri)
634
+ end
635
+ run(sync: sync, from: from)
636
+ while depth > 0 && target.children.size == 1
637
+ entry = target.children.first
638
+ break unless entry.directory?
639
+
640
+ dest = target.join(File.basename(file.path))
641
+ FileUtils.mv(entry, dest)
642
+ dest.children.each { |file| FileUtils.mv(file, target) }
643
+ dest.rmdir
644
+ target = entry
645
+ depth -= 1
646
+ end
647
+ ensure
648
+ file&.unlink
649
+ end
650
+ end
651
+
505
652
  def first(key, *args, **kwargs, &blk)
506
653
  event(:first, key, *args, **kwargs, &blk)
507
654
  end
@@ -514,17 +661,38 @@ module Squared
514
661
  event(:error, key, *args, **kwargs, &blk)
515
662
  end
516
663
 
517
- def event(name, key, *args, **kwargs, &blk)
518
- (@events[name.to_sym] ||= {})[key.to_sym] = [block_given? ? [blk] + args : args, kwargs]
664
+ def event(name, key, *args, override: false, **kwargs, &blk)
665
+ data = @events[name.to_sym] ||= {}
666
+ items = if override
667
+ data[key.to_sym] = []
668
+ else
669
+ data[key.to_sym] ||= []
670
+ end
671
+ items << [block_given? ? [blk] + args : args, kwargs]
672
+ self
519
673
  end
520
674
 
521
675
  def as(cmd, script, to = nil)
522
676
  script = { "#{script}": to } if to
523
677
  data = (@as ||= {})[cmd.to_sym] ||= {}
524
678
  script.each { |key, val| data[key.to_s] = val }
679
+ self
680
+ end
681
+
682
+ def series(key, override: false, &blk)
683
+ if blocks.include?(key.to_sym) && block_given?
684
+ if !override && series?(target = instance_variable_get(:"@#{key}"))
685
+ target << blk
686
+ else
687
+ instance_variable_set :"@#{key}", [blk]
688
+ end
689
+ else
690
+ log&.warn "series: @#{key} (invalid)"
691
+ end
692
+ self
525
693
  end
526
694
 
527
- def variable_set(key, *val, **kwargs)
695
+ def variable_set(key, *val, **kwargs, &blk)
528
696
  if variables.include?(key)
529
697
  case key
530
698
  when :build, :run
@@ -545,17 +713,22 @@ module Squared
545
713
  when :dependfile
546
714
  @dependfile = basepath(*val)
547
715
  else
548
- instance_variable_set :"@#{key}", val.first
716
+ if blocks.include?(key) && block_given?
717
+ series key, &blk
718
+ else
719
+ instance_variable_set(:"@#{key}", block_given? && val.empty? ? blk : val.first)
720
+ end
549
721
  end
550
722
  else
551
723
  log&.warn "variable_set: @#{key} (private)"
552
724
  end
725
+ self
553
726
  end
554
727
 
555
728
  def enabled?(ref = nil, **)
556
729
  return false if ref && !ref?(ref)
557
730
 
558
- path.directory? && !path.empty?
731
+ (path.directory? && !path.empty?) || archive?
559
732
  end
560
733
 
561
734
  def has?(meth, ref = nil)
@@ -569,7 +742,7 @@ module Squared
569
742
  end
570
743
 
571
744
  def build?
572
- !!@output[0] || script?
745
+ !!@output[0] || script? || series?(@run)
573
746
  end
574
747
 
575
748
  def script?
@@ -580,6 +753,10 @@ module Squared
580
753
  !!@depend
581
754
  end
582
755
 
756
+ def archive?
757
+ @archive.is_a?(Hash) && (!path.exist? || path.empty?)
758
+ end
759
+
583
760
  def graph?
584
761
  @graph.is_a?(Array) && !@graph.empty?
585
762
  end
@@ -640,8 +817,12 @@ module Squared
640
817
  @ref.reverse_each
641
818
  end
642
819
 
643
- def basepath(*args, ascend: nil)
644
- ret = path.join(*args)
820
+ def basepath(*args)
821
+ path.join(*args)
822
+ end
823
+
824
+ def rootpath(*args, ascend: nil)
825
+ ret = basepath(*args)
645
826
  return ret unless ascend && !ret.exist?
646
827
 
647
828
  path.parent.ascend.each do |dir|
@@ -676,8 +857,10 @@ module Squared
676
857
 
677
858
  def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, chdir: path, from: nil, **)
678
859
  unless cmd
679
- from &&= from.to_s
680
- warn log_message(Logger::WARN, from || 'unknown', subject: project, hint: 'no command given')
860
+ if warning?
861
+ from &&= from.to_s
862
+ warn log_message(Logger::WARN, from || 'unknown', subject: project, hint: 'no command given', pass: true)
863
+ end
681
864
  return
682
865
  end
683
866
  cmd = session_done(cmd)
@@ -688,7 +871,7 @@ module Squared
688
871
  log&.warn "ENV was discarded: #{var}" if var
689
872
  task_invoke(cmd, exception: exception, warning: warning?)
690
873
  else
691
- print_item format_banner(cmd, banner: banner) if sync && !env('BANNER', equals: '0')
874
+ print_item format_banner(cmd, banner: banner) if sync
692
875
  args = var.is_a?(Hash) ? [var, cmd] : [cmd]
693
876
  shell(*args, chdir: chdir, exception: exception)
694
877
  end
@@ -713,18 +896,23 @@ module Squared
713
896
  end
714
897
 
715
898
  def run_b(obj, from: nil, sync: true)
716
- return unless obj
717
-
718
- if obj.is_a?(Array) && obj.any? { |val| !val.is_a?(String) }
719
- build(*obj, from: from, sync: sync)
899
+ case obj
900
+ when Proc, Method
901
+ obj.call
720
902
  else
721
- run_s(obj.is_a?(Enumerable) ? obj.to_a : obj, from: from, sync: sync)
903
+ if series?(obj)
904
+ obj.each(&:call)
905
+ elsif obj.is_a?(Array) && obj.any? { |val| !val.is_a?(String) }
906
+ build(*obj, from: from, sync: sync)
907
+ elsif obj
908
+ run_s(obj.is_a?(Enumerable) ? obj.to_a : obj, from: from, sync: sync)
909
+ end
722
910
  end
723
911
  end
724
912
 
725
913
  def graph_branch(target, data, tasks = nil, out = nil, sync: true, pass: [], done: [], depth: 0,
726
914
  single: false, last: false, context: nil)
727
- tag = ->(proj) { "#{proj.name}#{SEM_VER =~ proj.version ? "@#{proj.version}" : ''}" }
915
+ tag = ->(proj) { "#{proj.name}#{SEM_VER.match?(proj.version) ? "@#{proj.version}" : ''}" }
728
916
  check = ->(deps) { deps.reject { |val| done.include?(val) } }
729
917
  dedupe = lambda do |name|
730
918
  next [] unless (ret = data[name])
@@ -764,7 +952,7 @@ module Squared
764
952
 
765
953
  t = dedupe.(proj.name)
766
954
  j = if out
767
- if i == items.size - 1 || check.(post = items[(i + 1)..-1]).empty?
955
+ if i == items.size - 1 || check.(post = items[i + 1..-1]).empty?
768
956
  true
769
957
  elsif !t.empty? && depth > 0
770
958
  post.reject { |pr| t.include?(pr) }.empty?
@@ -782,13 +970,7 @@ module Squared
782
970
  next if pass.include?(meth)
783
971
 
784
972
  if workspace.task_defined?(cmd = task_join(proj.name, meth))
785
- if ENV.key?(key = "BANNER_#{proj.name.upcase}")
786
- key = nil
787
- else
788
- ENV[key] = '0'
789
- end
790
973
  run(cmd, sync: false, banner: false)
791
- ENV.delete(key) if key
792
974
  elsif proj.has?(meth, tasks || group ? nil : workspace.baseref)
793
975
  proj.__send__(meth.to_sym, sync: sync)
794
976
  end
@@ -802,7 +984,7 @@ module Squared
802
984
  k = 0
803
985
  final = data.keys.last
804
986
  while k < depth
805
- indent = k > 0 ? ((last && !j) || (j && k == depth - 1) || single) : j && last && depth == 1
987
+ indent = k > 0 && ((last && !j) || (j && k == depth - 1) || single)
806
988
  s += "#{indent || (last && data[final].last == context) ? ' ' : a} "
807
989
  k += 1
808
990
  end
@@ -814,7 +996,7 @@ module Squared
814
996
  done
815
997
  end
816
998
 
817
- def graph_collect(target, start = [], data: {}, pass: [], root: [])
999
+ def graph_collect(target, start = [], data: {}, pass: [])
818
1000
  deps = []
819
1001
  (start.empty? ? target.instance_variable_get(:@graph) : start)&.each do |val|
820
1002
  next if pass.include?(val)
@@ -826,21 +1008,18 @@ module Squared
826
1008
  else
827
1009
  items = workspace.find(group: val, ref: val.to_sym)
828
1010
  end
1011
+
829
1012
  items.each do |proj|
830
- next if pass.include?(name = proj.name)
1013
+ next if pass.include?(proj.name)
831
1014
 
832
- if proj.graph? && !data.key?(name) && !root.include?(name)
833
- graph_collect(proj, data: data, pass: pass, root: root + [name, target.name])
834
- end
835
- next if (objs = data.fetch(name, [])).include?(target)
1015
+ graph_collect(proj, data: data, pass: pass) if proj.graph? && !data.key?(proj.name)
1016
+ next if (objs = data.fetch(proj.name, [])).include?(target)
836
1017
 
837
1018
  deps << proj
838
- deps.concat(objs)
1019
+ deps += objs
839
1020
  end
840
1021
  end
841
- deps.uniq!
842
- deps.delete(target)
843
- data[target.name] = deps
1022
+ data[target.name] = deps.uniq.reject { |proj| proj == target }
844
1023
  data
845
1024
  end
846
1025
 
@@ -860,7 +1039,7 @@ module Squared
860
1039
  end
861
1040
 
862
1041
  def session(*cmd, prefix: cmd.first, main: true, options: true)
863
- prefix = stripext(prefix.to_s).upcase
1042
+ prefix = prefix.to_s.upcase
864
1043
  if (val = PATH[prefix] || PATH[prefix.to_sym])
865
1044
  cmd[0] = shell_quote(val, force: false)
866
1045
  end
@@ -874,8 +1053,7 @@ module Squared
874
1053
  def session_delete(*list, target: @session)
875
1054
  ret = []
876
1055
  list.each do |val|
877
- pat = /^#{Regexp.escape(shell_option(val))}(?: |=|$)/
878
- if (key = target.find { |opt| opt =~ pat })
1056
+ if (key = target.find { |opt| opt.match?(/^#{Regexp.escape(shell_option(val))}(?: |=|$)/o) })
879
1057
  target.delete(key)
880
1058
  ret << key
881
1059
  end
@@ -890,30 +1068,33 @@ module Squared
890
1068
  def session_done(cmd)
891
1069
  return cmd unless cmd.respond_to?(:done)
892
1070
 
893
- raise_error('no args were added', hint: cmd.first || name) unless cmd.size > 1
1071
+ raise_error('no args added', hint: cmd.first || name) unless cmd.size > 1
894
1072
  @session = nil if cmd == @session
895
1073
  cmd.done
896
1074
  end
897
1075
 
898
- def option(*args, target: @session, prefix: target&.first, **kwargs)
1076
+ def option(*args, prefix: @session&.first, **kwargs)
899
1077
  if prefix
900
1078
  args.each do |val|
901
- ret = env("#{stripext(prefix)}_#{val.gsub(/\W/, '_')}".upcase, **kwargs)
1079
+ ret = env("#{prefix}_#{val.gsub(/\W/, '_')}".upcase, **kwargs)
902
1080
  return ret if ret
903
1081
  end
904
1082
  end
905
1083
  nil
906
1084
  end
907
1085
 
908
- def option_sanitize(opts, list, target: @session, no: nil, args: false, first: false, pass: ['='])
1086
+ def option_sanitize(opts, list, target: @session, no: nil, first: false, pass: ['='])
909
1087
  ret = []
910
1088
  reg = []
911
1089
  bare = []
912
1090
  e = []
913
1091
  b = []
1092
+ m = []
914
1093
  p = []
915
1094
  q = []
1095
+ qq = []
916
1096
  i = []
1097
+ f = []
917
1098
  list = list.map do |val|
918
1099
  x, y = val.split('|')
919
1100
  if y
@@ -933,16 +1114,19 @@ module Squared
933
1114
  e << flag
934
1115
  when 'b'
935
1116
  b << flag
1117
+ when 'm'
1118
+ m << flag
936
1119
  when 'q'
1120
+ qq << flag if val[n + 2] == 'q'
937
1121
  q << flag
938
1122
  when 'p'
939
1123
  p << flag
940
1124
  when 'i'
941
1125
  i << flag
942
- when 'v'
943
- reg << Regexp.escape(flag)
1126
+ when 'f'
1127
+ f << flag
944
1128
  else
945
- next
1129
+ reg << Regexp.escape(flag)
946
1130
  end
947
1131
  bare << flag if val.end_with?('?')
948
1132
  else
@@ -960,40 +1144,41 @@ module Squared
960
1144
  elsif opt.start_with?('no-') && no.include?(name = opt[3..-1])
961
1145
  target << "--no-#{name}"
962
1146
  else
963
- if opt =~ /\A([^=]+)=(.+)\z/
964
- a = $1
965
- if e.include?(a)
966
- target << shell_option(a, $2)
967
- elsif q.include?(a)
968
- target << quote_option(a, $2)
969
- elsif p.include?(a)
970
- target << quote_option(a, basepath($2))
971
- elsif b.include?(a) || (i.include?(a) && /^\d+$/.match?($2))
972
- target << basic_option(a, $2)
1147
+ if opt =~ /^([^=]+)=(.+)$/
1148
+ key = $1
1149
+ val = $2
1150
+ match = ->(flag, pat) { flag.include?(key) && pat.match?(val) }
1151
+ if e.include?(key)
1152
+ target << shell_option(key, val)
1153
+ elsif q.include?(key)
1154
+ target << quote_option(key, val, double: qq.include?(key))
1155
+ elsif p.include?(key)
1156
+ target << quote_option(key, basepath(val))
1157
+ elsif m.include?(key)
1158
+ target << basic_option(key, val, merge: true)
1159
+ elsif b.include?(key) || match.(i, /^\d+$/) || match.(f, /^\d*(?:\.\d+)?$/)
1160
+ target << basic_option(key, val)
973
1161
  else
974
1162
  ret << opt
975
1163
  end
976
- opt = a
1164
+ opt = key
977
1165
  else
978
1166
  ret << opt
979
- found = true if args
980
1167
  end
981
- found = true if first && pass.none? { |val| opt.include?(val) }
1168
+ found = true if first && pass.none? { |s| opt.include?(s) }
982
1169
  end
983
1170
  end
984
- [ret, reg.empty? ? /\A\s+\z/ : /\A(#{reg.join('|')})=(.+)\z/]
1171
+ [ret, reg.empty? ? /\A\s+\z/ : /^(#{reg.join('|')})=(.+)$/]
985
1172
  end
986
1173
 
987
1174
  def option_clear(opts, target: @session, **kwargs)
988
- return if opts.empty?
989
-
990
1175
  kwargs[:subject] ||= target&.first
991
- kwargs[:hint] ||= 'unrecognized'
992
- warn log_message(:warn, opts.join(', '), **kwargs)
1176
+ kwargs[:hint] ||= 'not used'
1177
+ warn log_message(Logger::WARN, opts.join(', '), pass: true, **kwargs) unless opts.empty?
993
1178
  end
994
1179
 
995
1180
  def print_item(*val)
996
- puts if @@print_order > 0
1181
+ puts if @@print_order > 0 && verbose && !stdin?
997
1182
  @@print_order += 1
998
1183
  puts val unless val.empty? || (val.size == 1 && val.first.nil?)
999
1184
  end
@@ -1019,7 +1204,7 @@ module Squared
1019
1204
  val
1020
1205
  end
1021
1206
  end
1022
- out << sub_style('-' * n, styles: border)
1207
+ out << sub_style(ARG[:BORDER][1] * n, styles: border)
1023
1208
  out.join("\n")
1024
1209
  end
1025
1210
 
@@ -1031,7 +1216,7 @@ module Squared
1031
1216
  sub.each { |h| s = sub_style(s, **h) }
1032
1217
  s
1033
1218
  end
1034
- ret = [sub_style('-' * n, styles: kwargs.key?(:border) ? kwargs[:border] : borderstyle), *lines]
1219
+ ret = [sub_style(ARG[:BORDER][1] * n, styles: kwargs.key?(:border) ? kwargs[:border] : borderstyle), *lines]
1035
1220
  ret.reverse! if reverse
1036
1221
  ret.join("\n")
1037
1222
  end
@@ -1057,7 +1242,7 @@ module Squared
1057
1242
  if verbose
1058
1243
  out = []
1059
1244
  if data[:command]
1060
- if /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+)) /.match(cmd)
1245
+ if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+)) /
1061
1246
  path = $3 || $2 || $1
1062
1247
  cmd = cmd.sub(path, File.basename(path).upcase)
1063
1248
  end
@@ -1095,9 +1280,9 @@ module Squared
1095
1280
  unless items.empty?
1096
1281
  pad = items.size.to_s.size
1097
1282
  items.each_with_index do |val, i|
1098
- next unless reg.empty? || reg.any? { |pat| val[0] =~ pat }
1283
+ next unless reg.empty? || reg.any? { |pat| val[0].match?(pat) }
1099
1284
 
1100
- out << "#{(i + 1).to_s.rjust(pad)}. #{each ? each.(val) : val[0]}"
1285
+ out << "#{i.succ.to_s.rjust(pad)}. #{each ? each.(val) : val[0]}"
1101
1286
  end
1102
1287
  end
1103
1288
  sub = [headerstyle]
@@ -1131,9 +1316,9 @@ module Squared
1131
1316
  opts.each { |val| target << shell_option(flag, val) }
1132
1317
  end
1133
1318
 
1134
- def append_hash(data, target: @session)
1319
+ def append_hash(data, target: @session, build: false)
1135
1320
  target ||= []
1136
- if (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
1321
+ if build && (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
1137
1322
  type = "__#{type}__"
1138
1323
  if (extra = data[type] || data[type.to_sym]).is_a?(Hash)
1139
1324
  data = data.merge(extra)
@@ -1162,7 +1347,7 @@ module Squared
1162
1347
  target
1163
1348
  end
1164
1349
 
1165
- def append_any(val, target: @session, delim: false)
1350
+ def append_any(val, target: @session, build: false, delim: false)
1166
1351
  if delim && !target.include?('--')
1167
1352
  target << '--'
1168
1353
  else
@@ -1172,7 +1357,7 @@ module Squared
1172
1357
  when String
1173
1358
  target << val
1174
1359
  when Hash
1175
- append_hash(val, target: target)
1360
+ append_hash(val, target: target, build: build)
1176
1361
  when Enumerable
1177
1362
  if target.is_a?(Array)
1178
1363
  target.concat(val.to_a)
@@ -1181,6 +1366,7 @@ module Squared
1181
1366
  end
1182
1367
  else
1183
1368
  target.delete('--') if delim
1369
+ nil
1184
1370
  end
1185
1371
  end
1186
1372
 
@@ -1189,8 +1375,9 @@ module Squared
1189
1375
 
1190
1376
  target << '--' if delim && !target.include?('--')
1191
1377
  list.map do |val|
1192
- target << (val = escape ? shell_escape(val, quote: quote) : shell_quote(val))
1193
- val
1378
+ item = escape ? shell_escape(val, quote: quote) : shell_quote(val)
1379
+ target << item
1380
+ item
1194
1381
  end
1195
1382
  end
1196
1383
 
@@ -1198,8 +1385,8 @@ module Squared
1198
1385
  **kwargs)
1199
1386
  return if (list = list.flatten).empty?
1200
1387
 
1201
- list.flatten.each do |opt|
1202
- next unless (val = option(opt, target: target, **kwargs))
1388
+ list.each do |opt|
1389
+ next unless (val = option(opt, **kwargs))
1203
1390
 
1204
1391
  return target << (if flag
1205
1392
  shell_option(opt, equals ? val : nil, quote: quote, escape: escape, force: force)
@@ -1215,7 +1402,7 @@ module Squared
1215
1402
  return if (list = list.flatten).empty?
1216
1403
 
1217
1404
  ret = []
1218
- list.flatten.each do |flag|
1405
+ list.each do |flag|
1219
1406
  next unless (val = option(flag, **kwargs))
1220
1407
 
1221
1408
  if val == '0' && no
@@ -1228,7 +1415,45 @@ module Squared
1228
1415
  end
1229
1416
 
1230
1417
  def append_nocolor(target: @session)
1231
- target << '--no-color' if !ARG[:COLOR] || stdin? || option('no-color', target: target, ignore: false)
1418
+ target << '--no-color' if !ARG[:COLOR] || stdin? || option('no-color', ignore: false)
1419
+ end
1420
+
1421
+ def merge_opts(base, data)
1422
+ return data unless base
1423
+ return base unless data
1424
+
1425
+ ret = case data
1426
+ when String
1427
+ case base
1428
+ when String
1429
+ "#{base} #{data}"
1430
+ when Hash
1431
+ "#{append_hash(base).join(' ')} #{data}"
1432
+ when Enumerable
1433
+ "#{base.to_a.join(' ')} #{data}"
1434
+ end
1435
+ when Hash
1436
+ case base
1437
+ when String
1438
+ "#{base} #{append_hash(data).join(' ')}"
1439
+ when Hash
1440
+ base.merge(data)
1441
+ when Enumerable
1442
+ base.to_a + append_hash(data)
1443
+ end
1444
+ when Enumerable
1445
+ case base
1446
+ when String
1447
+ "#{base} #{data.to_a.join(' ')}"
1448
+ when Hash
1449
+ "#{append_hash(base).join(' ')} #{data.to_a.join(' ')}"
1450
+ when Enumerable
1451
+ base.to_a + data.to_a
1452
+ end
1453
+ else
1454
+ base
1455
+ end
1456
+ ret || data
1232
1457
  end
1233
1458
 
1234
1459
  def collect_hash(data, pass: [])
@@ -1237,10 +1462,19 @@ module Squared
1237
1462
  ret
1238
1463
  end
1239
1464
 
1240
- def param_guard(action, flag, args: nil, key: nil, pat: nil)
1465
+ def parse_json(val, hint: nil)
1466
+ ret = JSON.parse(val)
1467
+ raise_error('invalid JSON object', val, hint: hint) unless ret.is_a?(Hash)
1468
+ rescue StandardError => e
1469
+ log&.warn e
1470
+ else
1471
+ ret
1472
+ end
1473
+
1474
+ def param_guard(action, flag, args: nil, key: nil, pat: nil, values: nil)
1241
1475
  if args && key
1242
- val = args.fetch(key, nil)
1243
- return val unless val.nil? || (pat && !val.match?(pat))
1476
+ val = args[key]
1477
+ return val unless val.nil? || (pat && !val.match?(pat)) || (values && !values.include?(val))
1244
1478
 
1245
1479
  @session = nil
1246
1480
  raise_error(action, "#{flag}[#{key}]", hint: val.nil? ? 'missing' : 'invalid')
@@ -1251,6 +1485,17 @@ module Squared
1251
1485
  args
1252
1486
  end
1253
1487
 
1488
+ def relativepath(*files, all: false)
1489
+ return [] if files.empty?
1490
+
1491
+ files.flatten.map { |val| Pathname.new(val) }.select { |val| projectpath?(val) }.map do |val|
1492
+ val = val.absolute? ? val.to_s.sub(/^#{Regexp.escape(File.join(path, ''))}/o, '') : val.to_s
1493
+ val = val[2..-1] if val.start_with?('./')
1494
+ val = "#{val}*" if all && val.end_with?('/')
1495
+ val
1496
+ end
1497
+ end
1498
+
1254
1499
  def projectmap(files, parent: false)
1255
1500
  files = files.select { |val| projectpath?(val) } unless parent
1256
1501
  files.map { |val| val == '.' ? '.' : shell_quote(basepath(val.strip)) }
@@ -1273,41 +1518,16 @@ module Squared
1273
1518
  fill ? semver(ret) : ret
1274
1519
  end
1275
1520
 
1276
- def semcmp(val, other)
1277
- return 0 if val == other
1278
-
1279
- a, b = [val, other].map! { |ver| ver.scan(SEM_VER) }
1280
- return -1 if b.empty?
1281
- return 1 if a.empty?
1282
-
1283
- a, b = [a.first, b.first].map! do |c|
1284
- begin
1285
- d = Integer(c[5]).to_s
1286
- rescue StandardError
1287
- d = c[5] ? '-1' : '0'
1288
- end
1289
- [c[0], c[2], c[4] || '0', d]
1290
- end
1291
- a.each_with_index do |c, index|
1292
- next if c == (d = b[index])
1293
-
1294
- return c.to_i < d.to_i ? 1 : -1
1295
- end
1296
- 0
1297
- end
1298
-
1299
1521
  def indexitem(val)
1300
- [$1.to_i, $2 && $2[1..-1]] if val =~ /\A#{Regexp.escape(indexchar)}(\d+)(:.+)?\z/
1522
+ return unless val =~ /\A\^(\d+)(:.+)?\z/
1523
+
1524
+ [$1.to_i, $2 && $2[1..-1]]
1301
1525
  end
1302
1526
 
1303
1527
  def indexerror(val, list = nil)
1304
1528
  raise_error("requested index #{val}", hint: list && "of #{list.size}")
1305
1529
  end
1306
1530
 
1307
- def indexchar
1308
- workspace.windows? ? '+' : '^'
1309
- end
1310
-
1311
1531
  def color(val)
1312
1532
  ret = theme[val]
1313
1533
  ret && !ret.empty? ? ret : [val]
@@ -1317,22 +1537,28 @@ module Squared
1317
1537
  val.compact.map { |s| color(s) }.flatten
1318
1538
  end
1319
1539
 
1540
+ def epochtime
1541
+ DateTime.now.strftime('%Q').to_i
1542
+ end
1543
+
1320
1544
  def on(event, from, *args, **kwargs)
1321
- return unless from && (obj = @events[event][from])
1545
+ return unless from
1322
1546
 
1323
- if obj.is_a?(Array) && obj[1].is_a?(Hash)
1324
- opts = kwargs.empty? ? obj[1] : obj[1].merge(kwargs)
1325
- target = obj[0]
1326
- else
1327
- opts = kwargs
1328
- target = obj
1329
- end
1330
- as_a(target, flat: true).each do |cmd|
1331
- case cmd
1332
- when Proc, Method
1333
- cmd.call(*args, **opts)
1334
- when String
1335
- run(cmd, **opts)
1547
+ @events[event][from]&.each do |obj|
1548
+ if obj.is_a?(Array) && obj[1].is_a?(Hash)
1549
+ opts = kwargs.empty? ? obj[1] : obj[1].merge(kwargs)
1550
+ target = obj[0]
1551
+ else
1552
+ opts = kwargs
1553
+ target = obj
1554
+ end
1555
+ as_a(target, flat: true).each do |cmd|
1556
+ case cmd
1557
+ when Proc, Method
1558
+ cmd.call(*args, **opts)
1559
+ when String
1560
+ run(cmd, **opts)
1561
+ end
1336
1562
  end
1337
1563
  end
1338
1564
  end
@@ -1341,8 +1567,7 @@ module Squared
1341
1567
  pwd = Pathname.pwd
1342
1568
  if block_given?
1343
1569
  begin
1344
- pass = semscan(pass).join <= RUBY_VERSION if pass.is_a?(String)
1345
- if (path == pwd || pass == true) && !workspace.jruby_win?
1570
+ if path == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join >= RUBY_VERSION)
1346
1571
  ret = instance_eval(&blk)
1347
1572
  else
1348
1573
  Dir.chdir(path)
@@ -1374,23 +1599,53 @@ module Squared
1374
1599
  end
1375
1600
 
1376
1601
  def run_set(cmd, val = nil, opts: nil, **)
1377
- return @output = cmd.dup if cmd.is_a?(Array)
1378
-
1379
- unless @output[1] == false && !@output[0].nil?
1602
+ diso = @output[1] == false && !@output[0].nil?
1603
+ dise = @output[2] == false
1604
+ parse = lambda do |data|
1605
+ ret = []
1606
+ if data[:command]
1607
+ ret[0] = data[:command]
1608
+ ret[1] = data[:opts] unless diso
1609
+ ret[3] = data[:args]
1610
+ elsif data[:script]
1611
+ ret[1] = data[:script]
1612
+ ret[3] = data[:opts]
1613
+ ret[4] = data[:args]
1614
+ else
1615
+ ret[0] = false
1616
+ end
1617
+ ret[2] = data[:env] unless dise
1618
+ ret
1619
+ end
1620
+ case cmd
1621
+ when Array
1622
+ @output = if cmd.all? { |data| data.is_a?(Hash) }
1623
+ diso = false
1624
+ dise = false
1625
+ cmd.map { |data| parse.(data) }
1626
+ else
1627
+ cmd.dup
1628
+ end
1629
+ return
1630
+ when Hash
1631
+ @output = parse.(data)
1632
+ else
1633
+ @output[0] = cmd
1634
+ end
1635
+ unless diso
1380
1636
  if opts == false
1381
1637
  @output[1] = false
1382
1638
  elsif opts && opts != true
1383
1639
  @output[1] = opts
1384
1640
  end
1385
1641
  end
1386
- unless @output[2] == false
1387
- if val.is_a?(Hash)
1388
- @output[2] = val
1389
- elsif val == false
1390
- @output[2] = false
1391
- end
1642
+ return if dise
1643
+
1644
+ if val.is_a?(Hash)
1645
+ @output[2] = val
1646
+ elsif val == false
1647
+ @output[2] = false
1392
1648
  end
1393
- @output[0] = cmd
1394
1649
  end
1395
1650
 
1396
1651
  def script_set(cmd, prod: nil, args: nil, **)
@@ -1406,6 +1661,8 @@ module Squared
1406
1661
  end
1407
1662
 
1408
1663
  def as_get(val)
1664
+ return unless val
1665
+
1409
1666
  @global && (ret = @as && @as[from] && @as[from][val]) ? ret : val
1410
1667
  end
1411
1668
 
@@ -1431,8 +1688,7 @@ module Squared
1431
1688
  end
1432
1689
 
1433
1690
  def projectpath?(val)
1434
- val = Pathname.new(val).cleanpath
1435
- val.absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?(File.join('..', ''))
1691
+ Pathname.new(val).absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?('..')
1436
1692
  end
1437
1693
 
1438
1694
  def semmajor?(cur, want)
@@ -1441,19 +1697,20 @@ module Squared
1441
1697
 
1442
1698
  def runnable?(val)
1443
1699
  case val
1444
- when String
1700
+ when String, Enumerable, Proc, Method
1445
1701
  true
1446
- when Enumerable
1447
- !val.is_a?(Hash)
1448
1702
  else
1449
1703
  false
1450
1704
  end
1451
1705
  end
1452
1706
 
1707
+ def series?(val)
1708
+ val.is_a?(Array) && val.all? { |p| p.is_a?(Proc) || p.is_a?(Method) }
1709
+ end
1710
+
1453
1711
  def session_arg?(*list, target: @session, value: false)
1454
1712
  list.any? do |val|
1455
- pat = /^#{Regexp.escape(shell_option(val))}#{value ? '[ =].' : '(?:[ =]|$)'}/
1456
- target.any? { |opt| opt =~ pat }
1713
+ target.any? { |opt| opt.match?(/^#{Regexp.escape(shell_option(val))}#{value ? '[ =].' : '(?:[ =]|$)'}/o) }
1457
1714
  end
1458
1715
  end
1459
1716
 
@@ -1490,6 +1747,10 @@ module Squared
1490
1747
  Base.tasks + VAR_SET
1491
1748
  end
1492
1749
 
1750
+ def blocks
1751
+ BLK_SET
1752
+ end
1753
+
1493
1754
  def borderstyle
1494
1755
  ((data = workspace.banner_get(*@ref, group: group)) && data[:border]) || theme[:border]
1495
1756
  end