squared 0.3.6 → 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
+ BLK_SET = %i[run depend doc lint test copy clean].freeze
18
19
  SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?\b/.freeze
19
- private_constant :VAR_SET, :SEM_VER
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)
@@ -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,7 +176,9 @@ 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, **)
@@ -192,12 +204,12 @@ module Squared
192
204
  raise if @exception
193
205
 
194
206
  file = nil
195
- warn log_message(Logger::WARN, e) if warning?
207
+ warn log_message(Logger::WARN, e, pass: true) if warning?
196
208
  end
197
209
  end
198
210
  log[:progname] ||= @name
199
211
  if (val = env('LOG_LEVEL', ignore: false))
200
- log[:level] = val
212
+ log[:level] = val.match?(/^\d$/) ? log_sym(val.to_i) : val
201
213
  end
202
214
  log.delete(:file)
203
215
  @log = [file, log]
@@ -206,20 +218,18 @@ module Squared
206
218
  def initialize_env(dev: nil, prod: nil, **)
207
219
  @dev = env_match('BUILD', dev, suffix: 'DEV', strict: true)
208
220
  @prod = env_match('BUILD', prod, suffix: 'PROD', strict: true)
209
- cmd = @output[0]
210
- if @output[2] != false && (val = env('BUILD', suffix: 'ENV'))
211
- begin
212
- data = JSON.parse(val)
213
- raise_error('invalid JSON object', val, hint: "BUILD_#{@envname}_ENV") unless data.is_a?(Hash)
214
- rescue StandardError => e
215
- log&.warn e
216
- else
217
- @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))
218
232
  end
219
- end
220
- if cmd != false && !cmd.is_a?(Array)
221
- @output[cmd ? 1 : 3] = shell_split(val, escape: false, join: true) if (val = env('BUILD', suffix: 'OPTS'))
222
- @output[4] = shell_split(val, escape: false, join: true) if !cmd && (val = env('SCRIPT', suffix: 'OPTS'))
223
233
  end
224
234
  @version = val if (val = env('BUILD', suffix: 'VERSION'))
225
235
  return unless (val = env('BUILD', strict: true))
@@ -254,14 +264,14 @@ module Squared
254
264
 
255
265
  format_desc action, flag, '(-)project*'
256
266
  task flag do |_, args|
257
- 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 })
258
268
  if flag == :run
259
269
  graph args
260
270
  else
261
271
  out, done = graph(args, out: [])
262
272
  out.map! do |val|
263
273
  done.each_with_index do |proj, i|
264
- next unless val =~ / #{Regexp.escape(proj.name)}(?:@\d|\z)/
274
+ next unless val.match?(/ #{Regexp.escape(proj.name)}(?:@\d|\z)/)
265
275
 
266
276
  val += " (#{i.succ})"
267
277
  break
@@ -275,6 +285,25 @@ module Squared
275
285
  ])
276
286
  end
277
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
278
307
  end
279
308
  end
280
309
  end
@@ -343,18 +372,19 @@ module Squared
343
372
 
344
373
  out = obj.link(self, *args, **kwargs, &blk) if obj.respond_to?(:link)
345
374
  if !out
346
- 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)
347
376
  elsif out.respond_to?(:build)
348
377
  out.build
349
378
  end
350
379
  self
351
380
  end
352
381
 
353
- def build(*args, sync: invoked_sync?('build'), from: :run, **)
382
+ def build(*args, sync: invoked_sync?('build'), from: @buildtype || :build, **)
354
383
  banner = verbose
355
384
  if args.empty?
356
- return unless from == :run
385
+ return unless from == (@buildtype || :build)
357
386
 
387
+ run_b(@run, sync: sync, from: from) if series?(@run)
358
388
  args = @output
359
389
  banner = verbose == 1 if task_invoked?('build', 'build:sync')
360
390
  end
@@ -365,7 +395,7 @@ module Squared
365
395
  a, b, c, d, e = val
366
396
  case b
367
397
  when Hash
368
- b = append_hash(b).join(' ')
398
+ b = append_hash(b, build: true).join(' ')
369
399
  when Enumerable
370
400
  b = b.to_a.join(' ')
371
401
  end
@@ -381,7 +411,7 @@ module Squared
381
411
  end
382
412
  cmd = cmd.join(' && ')
383
413
  else
384
- cmd, opts, var, flags, scr = args
414
+ cmd, opts, var, flags, extra = args
385
415
  end
386
416
  if cmd
387
417
  cmd = as_get(cmd)
@@ -389,7 +419,7 @@ module Squared
389
419
  flags = append_hash(flags).join(' ') if flags.is_a?(Hash)
390
420
  case opts
391
421
  when Hash
392
- opts = append_hash(opts)
422
+ opts = append_hash(opts, build: true)
393
423
  cmd = as_a(cmd).push(flags).concat(opts).compact.join(' ')
394
424
  when Enumerable
395
425
  cmd = as_a(cmd).concat(opts.to_a)
@@ -399,9 +429,9 @@ module Squared
399
429
  cmd = [cmd, flags, opts].compact.join(' ') if opts || flags
400
430
  end
401
431
  else
402
- return unless respond_to?(:compose)
432
+ return unless (opts || extra) && respond_to?(:compose)
403
433
 
404
- cmd = compose(as_get(opts), flags, script: true, args: scr, from: from)
434
+ cmd = compose(as_get(opts), flags, script: true, args: extra, from: from)
405
435
  end
406
436
  run(cmd, var, from: from, banner: banner, sync: sync)
407
437
  end
@@ -410,6 +440,12 @@ module Squared
410
440
  run_b(@depend, sync: sync, from: :depend)
411
441
  end
412
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
+
413
449
  def doc(*, sync: invoked_sync?('doc'), **)
414
450
  run_b(@doc, sync: sync, from: :doc)
415
451
  end
@@ -439,27 +475,31 @@ module Squared
439
475
  rescue StandardError => e
440
476
  log&.error e
441
477
  ret = on(:error, from, e)
442
- raise if @exception && ret != true
478
+ raise if exception && ret != true
443
479
  end
444
- when Enumerable
445
- as_a(@clean).each do |val|
446
- val = val.to_s
447
- path = basepath(val)
448
- if path.directory? && val =~ %r{[\\/]\z}
449
- log&.warn "rm -rf #{path}"
450
- path.rmtree(verbose: verbose)
451
- else
452
- log&.warn "rm #{path}"
453
- (val.include?('*') ? Dir[path] : [path]).each do |file|
454
- next unless File.file?(file)
455
-
456
- begin
457
- File.delete(file)
458
- rescue StandardError => e
459
- 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
460
498
  end
461
499
  end
462
500
  end
501
+ else
502
+ run_b(@clean, sync: sync)
463
503
  end
464
504
  end
465
505
  on :last, :clean
@@ -496,6 +536,119 @@ module Squared
496
536
  end
497
537
  end
498
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
+
499
652
  def first(key, *args, **kwargs, &blk)
500
653
  event(:first, key, *args, **kwargs, &blk)
501
654
  end
@@ -508,17 +661,38 @@ module Squared
508
661
  event(:error, key, *args, **kwargs, &blk)
509
662
  end
510
663
 
511
- def event(name, key, *args, **kwargs, &blk)
512
- (@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
513
673
  end
514
674
 
515
675
  def as(cmd, script, to = nil)
516
676
  script = { "#{script}": to } if to
517
677
  data = (@as ||= {})[cmd.to_sym] ||= {}
518
678
  script.each { |key, val| data[key.to_s] = val }
679
+ self
519
680
  end
520
681
 
521
- def variable_set(key, *val, **kwargs)
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
693
+ end
694
+
695
+ def variable_set(key, *val, **kwargs, &blk)
522
696
  if variables.include?(key)
523
697
  case key
524
698
  when :build, :run
@@ -539,17 +713,22 @@ module Squared
539
713
  when :dependfile
540
714
  @dependfile = basepath(*val)
541
715
  else
542
- 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
543
721
  end
544
722
  else
545
723
  log&.warn "variable_set: @#{key} (private)"
546
724
  end
725
+ self
547
726
  end
548
727
 
549
728
  def enabled?(ref = nil, **)
550
729
  return false if ref && !ref?(ref)
551
730
 
552
- path.directory? && !path.empty?
731
+ (path.directory? && !path.empty?) || archive?
553
732
  end
554
733
 
555
734
  def has?(meth, ref = nil)
@@ -563,7 +742,7 @@ module Squared
563
742
  end
564
743
 
565
744
  def build?
566
- !!@output[0] || script?
745
+ !!@output[0] || script? || series?(@run)
567
746
  end
568
747
 
569
748
  def script?
@@ -574,6 +753,10 @@ module Squared
574
753
  !!@depend
575
754
  end
576
755
 
756
+ def archive?
757
+ @archive.is_a?(Hash) && (!path.exist? || path.empty?)
758
+ end
759
+
577
760
  def graph?
578
761
  @graph.is_a?(Array) && !@graph.empty?
579
762
  end
@@ -634,8 +817,12 @@ module Squared
634
817
  @ref.reverse_each
635
818
  end
636
819
 
637
- def basepath(*args, ascend: nil)
638
- 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)
639
826
  return ret unless ascend && !ret.exist?
640
827
 
641
828
  path.parent.ascend.each do |dir|
@@ -672,7 +859,7 @@ module Squared
672
859
  unless cmd
673
860
  if warning?
674
861
  from &&= from.to_s
675
- warn log_message(Logger::WARN, from || 'unknown', subject: project, hint: 'no command given')
862
+ warn log_message(Logger::WARN, from || 'unknown', subject: project, hint: 'no command given', pass: true)
676
863
  end
677
864
  return
678
865
  end
@@ -684,7 +871,7 @@ module Squared
684
871
  log&.warn "ENV was discarded: #{var}" if var
685
872
  task_invoke(cmd, exception: exception, warning: warning?)
686
873
  else
687
- print_item format_banner(cmd, banner: banner) if sync && !env('BANNER', equals: '0')
874
+ print_item format_banner(cmd, banner: banner) if sync
688
875
  args = var.is_a?(Hash) ? [var, cmd] : [cmd]
689
876
  shell(*args, chdir: chdir, exception: exception)
690
877
  end
@@ -709,18 +896,23 @@ module Squared
709
896
  end
710
897
 
711
898
  def run_b(obj, from: nil, sync: true)
712
- return unless obj
713
-
714
- if obj.is_a?(Array) && obj.any? { |val| !val.is_a?(String) }
715
- build(*obj, from: from, sync: sync)
899
+ case obj
900
+ when Proc, Method
901
+ obj.call
716
902
  else
717
- 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
718
910
  end
719
911
  end
720
912
 
721
913
  def graph_branch(target, data, tasks = nil, out = nil, sync: true, pass: [], done: [], depth: 0,
722
914
  single: false, last: false, context: nil)
723
- tag = ->(proj) { "#{proj.name}#{SEM_VER =~ proj.version ? "@#{proj.version}" : ''}" }
915
+ tag = ->(proj) { "#{proj.name}#{SEM_VER.match?(proj.version) ? "@#{proj.version}" : ''}" }
724
916
  check = ->(deps) { deps.reject { |val| done.include?(val) } }
725
917
  dedupe = lambda do |name|
726
918
  next [] unless (ret = data[name])
@@ -778,13 +970,7 @@ module Squared
778
970
  next if pass.include?(meth)
779
971
 
780
972
  if workspace.task_defined?(cmd = task_join(proj.name, meth))
781
- if ENV.key?(key = "BANNER_#{proj.name.upcase}")
782
- key = nil
783
- else
784
- ENV[key] = '0'
785
- end
786
973
  run(cmd, sync: false, banner: false)
787
- ENV.delete(key) if key
788
974
  elsif proj.has?(meth, tasks || group ? nil : workspace.baseref)
789
975
  proj.__send__(meth.to_sym, sync: sync)
790
976
  end
@@ -867,8 +1053,7 @@ module Squared
867
1053
  def session_delete(*list, target: @session)
868
1054
  ret = []
869
1055
  list.each do |val|
870
- pat = /^#{Regexp.escape(shell_option(val))}(?: |=|$)/
871
- if (key = target.find { |opt| opt =~ pat })
1056
+ if (key = target.find { |opt| opt.match?(/^#{Regexp.escape(shell_option(val))}(?: |=|$)/o) })
872
1057
  target.delete(key)
873
1058
  ret << key
874
1059
  end
@@ -883,7 +1068,7 @@ module Squared
883
1068
  def session_done(cmd)
884
1069
  return cmd unless cmd.respond_to?(:done)
885
1070
 
886
- 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
887
1072
  @session = nil if cmd == @session
888
1073
  cmd.done
889
1074
  end
@@ -904,9 +1089,12 @@ module Squared
904
1089
  bare = []
905
1090
  e = []
906
1091
  b = []
1092
+ m = []
907
1093
  p = []
908
1094
  q = []
1095
+ qq = []
909
1096
  i = []
1097
+ f = []
910
1098
  list = list.map do |val|
911
1099
  x, y = val.split('|')
912
1100
  if y
@@ -926,12 +1114,17 @@ module Squared
926
1114
  e << flag
927
1115
  when 'b'
928
1116
  b << flag
1117
+ when 'm'
1118
+ m << flag
929
1119
  when 'q'
1120
+ qq << flag if val[n + 2] == 'q'
930
1121
  q << flag
931
1122
  when 'p'
932
1123
  p << flag
933
1124
  when 'i'
934
1125
  i << flag
1126
+ when 'f'
1127
+ f << flag
935
1128
  else
936
1129
  reg << Regexp.escape(flag)
937
1130
  end
@@ -952,23 +1145,27 @@ module Squared
952
1145
  target << "--no-#{name}"
953
1146
  else
954
1147
  if opt =~ /^([^=]+)=(.+)$/
955
- a = $1
956
- if e.include?(a)
957
- target << shell_option(a, $2)
958
- elsif q.include?(a)
959
- target << quote_option(a, $2)
960
- elsif p.include?(a)
961
- target << quote_option(a, basepath($2))
962
- elsif b.include?(a) || (i.include?(a) && /^\d+$/.match?($2))
963
- target << basic_option(a, $2)
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)
964
1161
  else
965
1162
  ret << opt
966
1163
  end
967
- opt = a
1164
+ opt = key
968
1165
  else
969
1166
  ret << opt
970
1167
  end
971
- found = true if first && pass.none? { |val| opt.include?(val) }
1168
+ found = true if first && pass.none? { |s| opt.include?(s) }
972
1169
  end
973
1170
  end
974
1171
  [ret, reg.empty? ? /\A\s+\z/ : /^(#{reg.join('|')})=(.+)$/]
@@ -977,7 +1174,7 @@ module Squared
977
1174
  def option_clear(opts, target: @session, **kwargs)
978
1175
  kwargs[:subject] ||= target&.first
979
1176
  kwargs[:hint] ||= 'not used'
980
- warn log_message(Logger::WARN, opts.join(', '), **kwargs) unless opts.empty?
1177
+ warn log_message(Logger::WARN, opts.join(', '), pass: true, **kwargs) unless opts.empty?
981
1178
  end
982
1179
 
983
1180
  def print_item(*val)
@@ -1007,7 +1204,7 @@ module Squared
1007
1204
  val
1008
1205
  end
1009
1206
  end
1010
- out << sub_style('-' * n, styles: border)
1207
+ out << sub_style(ARG[:BORDER][1] * n, styles: border)
1011
1208
  out.join("\n")
1012
1209
  end
1013
1210
 
@@ -1019,7 +1216,7 @@ module Squared
1019
1216
  sub.each { |h| s = sub_style(s, **h) }
1020
1217
  s
1021
1218
  end
1022
- 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]
1023
1220
  ret.reverse! if reverse
1024
1221
  ret.join("\n")
1025
1222
  end
@@ -1045,7 +1242,7 @@ module Squared
1045
1242
  if verbose
1046
1243
  out = []
1047
1244
  if data[:command]
1048
- if /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+)) /.match(cmd)
1245
+ if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+)) /
1049
1246
  path = $3 || $2 || $1
1050
1247
  cmd = cmd.sub(path, File.basename(path).upcase)
1051
1248
  end
@@ -1083,9 +1280,9 @@ module Squared
1083
1280
  unless items.empty?
1084
1281
  pad = items.size.to_s.size
1085
1282
  items.each_with_index do |val, i|
1086
- next unless reg.empty? || reg.any? { |pat| val[0] =~ pat }
1283
+ next unless reg.empty? || reg.any? { |pat| val[0].match?(pat) }
1087
1284
 
1088
- 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]}"
1089
1286
  end
1090
1287
  end
1091
1288
  sub = [headerstyle]
@@ -1119,9 +1316,9 @@ module Squared
1119
1316
  opts.each { |val| target << shell_option(flag, val) }
1120
1317
  end
1121
1318
 
1122
- def append_hash(data, target: @session)
1319
+ def append_hash(data, target: @session, build: false)
1123
1320
  target ||= []
1124
- if (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
1321
+ if build && (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
1125
1322
  type = "__#{type}__"
1126
1323
  if (extra = data[type] || data[type.to_sym]).is_a?(Hash)
1127
1324
  data = data.merge(extra)
@@ -1150,7 +1347,7 @@ module Squared
1150
1347
  target
1151
1348
  end
1152
1349
 
1153
- def append_any(val, target: @session, delim: false)
1350
+ def append_any(val, target: @session, build: false, delim: false)
1154
1351
  if delim && !target.include?('--')
1155
1352
  target << '--'
1156
1353
  else
@@ -1160,7 +1357,7 @@ module Squared
1160
1357
  when String
1161
1358
  target << val
1162
1359
  when Hash
1163
- append_hash(val, target: target)
1360
+ append_hash(val, target: target, build: build)
1164
1361
  when Enumerable
1165
1362
  if target.is_a?(Array)
1166
1363
  target.concat(val.to_a)
@@ -1169,6 +1366,7 @@ module Squared
1169
1366
  end
1170
1367
  else
1171
1368
  target.delete('--') if delim
1369
+ nil
1172
1370
  end
1173
1371
  end
1174
1372
 
@@ -1177,8 +1375,9 @@ module Squared
1177
1375
 
1178
1376
  target << '--' if delim && !target.include?('--')
1179
1377
  list.map do |val|
1180
- target << (val = escape ? shell_escape(val, quote: quote) : shell_quote(val))
1181
- val
1378
+ item = escape ? shell_escape(val, quote: quote) : shell_quote(val)
1379
+ target << item
1380
+ item
1182
1381
  end
1183
1382
  end
1184
1383
 
@@ -1186,7 +1385,7 @@ module Squared
1186
1385
  **kwargs)
1187
1386
  return if (list = list.flatten).empty?
1188
1387
 
1189
- list.flatten.each do |opt|
1388
+ list.each do |opt|
1190
1389
  next unless (val = option(opt, **kwargs))
1191
1390
 
1192
1391
  return target << (if flag
@@ -1203,7 +1402,7 @@ module Squared
1203
1402
  return if (list = list.flatten).empty?
1204
1403
 
1205
1404
  ret = []
1206
- list.flatten.each do |flag|
1405
+ list.each do |flag|
1207
1406
  next unless (val = option(flag, **kwargs))
1208
1407
 
1209
1408
  if val == '0' && no
@@ -1219,16 +1418,63 @@ module Squared
1219
1418
  target << '--no-color' if !ARG[:COLOR] || stdin? || option('no-color', ignore: false)
1220
1419
  end
1221
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
1457
+ end
1458
+
1222
1459
  def collect_hash(data, pass: [])
1223
1460
  ret = []
1224
1461
  data.each { |key, val| ret += val unless pass.include?(key) }
1225
1462
  ret
1226
1463
  end
1227
1464
 
1228
- 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)
1229
1475
  if args && key
1230
1476
  val = args[key]
1231
- return val unless val.nil? || (pat && !val.match?(pat))
1477
+ return val unless val.nil? || (pat && !val.match?(pat)) || (values && !values.include?(val))
1232
1478
 
1233
1479
  @session = nil
1234
1480
  raise_error(action, "#{flag}[#{key}]", hint: val.nil? ? 'missing' : 'invalid')
@@ -1239,6 +1485,17 @@ module Squared
1239
1485
  args
1240
1486
  end
1241
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
+
1242
1499
  def projectmap(files, parent: false)
1243
1500
  files = files.select { |val| projectpath?(val) } unless parent
1244
1501
  files.map { |val| val == '.' ? '.' : shell_quote(basepath(val.strip)) }
@@ -1280,22 +1537,28 @@ module Squared
1280
1537
  val.compact.map { |s| color(s) }.flatten
1281
1538
  end
1282
1539
 
1540
+ def epochtime
1541
+ DateTime.now.strftime('%Q').to_i
1542
+ end
1543
+
1283
1544
  def on(event, from, *args, **kwargs)
1284
- return unless from && (obj = @events[event][from])
1545
+ return unless from
1285
1546
 
1286
- if obj.is_a?(Array) && obj[1].is_a?(Hash)
1287
- opts = kwargs.empty? ? obj[1] : obj[1].merge(kwargs)
1288
- target = obj[0]
1289
- else
1290
- opts = kwargs
1291
- target = obj
1292
- end
1293
- as_a(target, flat: true).each do |cmd|
1294
- case cmd
1295
- when Proc, Method
1296
- cmd.call(*args, **opts)
1297
- when String
1298
- 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
1299
1562
  end
1300
1563
  end
1301
1564
  end
@@ -1336,23 +1599,53 @@ module Squared
1336
1599
  end
1337
1600
 
1338
1601
  def run_set(cmd, val = nil, opts: nil, **)
1339
- return @output = cmd.dup if cmd.is_a?(Array)
1340
-
1341
- 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
1342
1636
  if opts == false
1343
1637
  @output[1] = false
1344
1638
  elsif opts && opts != true
1345
1639
  @output[1] = opts
1346
1640
  end
1347
1641
  end
1348
- unless @output[2] == false
1349
- if val.is_a?(Hash)
1350
- @output[2] = val
1351
- elsif val == false
1352
- @output[2] = false
1353
- end
1642
+ return if dise
1643
+
1644
+ if val.is_a?(Hash)
1645
+ @output[2] = val
1646
+ elsif val == false
1647
+ @output[2] = false
1354
1648
  end
1355
- @output[0] = cmd
1356
1649
  end
1357
1650
 
1358
1651
  def script_set(cmd, prod: nil, args: nil, **)
@@ -1368,6 +1661,8 @@ module Squared
1368
1661
  end
1369
1662
 
1370
1663
  def as_get(val)
1664
+ return unless val
1665
+
1371
1666
  @global && (ret = @as && @as[from] && @as[from][val]) ? ret : val
1372
1667
  end
1373
1668
 
@@ -1402,19 +1697,20 @@ module Squared
1402
1697
 
1403
1698
  def runnable?(val)
1404
1699
  case val
1405
- when String
1700
+ when String, Enumerable, Proc, Method
1406
1701
  true
1407
- when Enumerable
1408
- !val.is_a?(Hash)
1409
1702
  else
1410
1703
  false
1411
1704
  end
1412
1705
  end
1413
1706
 
1707
+ def series?(val)
1708
+ val.is_a?(Array) && val.all? { |p| p.is_a?(Proc) || p.is_a?(Method) }
1709
+ end
1710
+
1414
1711
  def session_arg?(*list, target: @session, value: false)
1415
1712
  list.any? do |val|
1416
- pat = /^#{Regexp.escape(shell_option(val))}#{value ? '[ =].' : '(?:[ =]|$)'}/
1417
- target.any? { |opt| opt =~ pat }
1713
+ target.any? { |opt| opt.match?(/^#{Regexp.escape(shell_option(val))}#{value ? '[ =].' : '(?:[ =]|$)'}/o) }
1418
1714
  end
1419
1715
  end
1420
1716
 
@@ -1451,6 +1747,10 @@ module Squared
1451
1747
  Base.tasks + VAR_SET
1452
1748
  end
1453
1749
 
1750
+ def blocks
1751
+ BLK_SET
1752
+ end
1753
+
1454
1754
  def borderstyle
1455
1755
  ((data = workspace.banner_get(*@ref, group: group)) && data[:border]) || theme[:border]
1456
1756
  end