squared 0.4.11 → 0.4.13

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.
@@ -9,6 +9,7 @@ module Squared
9
9
  module Workspace
10
10
  module Project
11
11
  class Base
12
+ include Comparable
12
13
  include Common::Format
13
14
  include System
14
15
  include Shell
@@ -17,7 +18,8 @@ module Squared
17
18
  include Support
18
19
  include Rake::DSL
19
20
 
20
- VAR_SET = %i[parent global envname dependfile dependindex theme run script env pass].freeze
21
+ VAR_SET = %i[parent global script index envname desc dependfile dependindex theme archive env dev prod graph
22
+ pass exclude].freeze
21
23
  BLK_SET = %i[run depend doc lint test copy clean].freeze
22
24
  SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?\b/.freeze
23
25
  URI_SCHEME = %r{^([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
@@ -31,7 +33,7 @@ module Squared
31
33
  def bannerargs(*); end
32
34
 
33
35
  def tasks
34
- (%i[build archive graph] + BLK_SET).freeze
36
+ (%i[build archive graph prereqs] + BLK_SET).freeze
35
37
  end
36
38
 
37
39
  def as_path(val)
@@ -63,6 +65,7 @@ module Squared
63
65
  end
64
66
 
65
67
  @@tasks = {}
68
+ @@graph = { _: [] }
66
69
  @@print_order = 0
67
70
 
68
71
  subtasks({
@@ -73,8 +76,8 @@ module Squared
73
76
  attr_reader :name, :project, :workspace, :path, :theme, :exception, :pipe, :verbose,
74
77
  :group, :parent, :dependfile
75
78
 
76
- def initialize(workspace, path, name, *, group: nil, graph: nil, pass: nil, exclude: nil, release: nil,
77
- archive: nil, verbose: nil, first: {}, last: {}, error: {}, common: ARG[:COMMON], **kwargs)
79
+ def initialize(workspace, path, name, *, group: nil, archive: nil, first: {}, last: {}, error: {},
80
+ common: ARG[:COMMON], **kwargs)
78
81
  @path = path
79
82
  @workspace = workspace
80
83
  @name = name.to_s.freeze
@@ -93,33 +96,11 @@ module Squared
93
96
  when Hash
94
97
  archive
95
98
  end
96
- @release = release
99
+ @release = kwargs[:release]
97
100
  @envname = @name.gsub(/[^\w]+/, '_').upcase.freeze
98
- @exception = env_bool(kwargs[:exception], workspace.exception, strict: true)
99
- @pipe = env_pipe(kwargs[:pipe], workspace.pipe, strict: true)
100
- @verbose = case verbose
101
- when NilClass
102
- workspace.verbose
103
- when String
104
- env_bool(verbose, workspace.verbose, strict: true, index: true)
105
- else
106
- verbose
107
- end
108
- @theme = if !@verbose
109
- {}
110
- elsif common
111
- workspace.theme
112
- else
113
- __get__(:theme)[:project][to_sym] ||= {}
114
- end
115
101
  @output = []
116
102
  @ref = []
117
103
  @children = []
118
- @graph = if graph
119
- as_a(graph, workspace.prefix ? ->(val) { workspace.task_name(val).to_sym } : :to_sym).freeze
120
- end
121
- @pass = (pass ? as_a(pass) : []).freeze
122
- @exclude = (exclude ? as_a(exclude, :to_sym) : []).freeze
123
104
  @events = {
124
105
  first: first,
125
106
  last: last,
@@ -128,7 +109,15 @@ module Squared
128
109
  @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
129
110
  @parent = nil
130
111
  @global = false
112
+ @index = -1
131
113
  run_set(kwargs[:run], kwargs[:env], opts: kwargs.fetch(:opts, true))
114
+ exception_set kwargs[:exception]
115
+ pipe_set kwargs[:pipe]
116
+ verbose_set kwargs[:verbose]
117
+ theme_set common
118
+ graph_set kwargs[:graph]
119
+ pass_set kwargs[:pass]
120
+ exclude_set kwargs[:exclude]
132
121
  initialize_ref Base.ref
133
122
  end
134
123
 
@@ -212,16 +201,11 @@ module Squared
212
201
  elsif (val = log[:file])
213
202
  file = val.is_a?(String) ? Time.now.strftime(val) : "#{@name}-#{Date.today}.log"
214
203
  end
215
- if file
216
- file = (val = env('LOG_DIR')) ? @workspace.home.join(val, file) : @workspace.home + file
217
- begin
218
- file = file.realdirpath
219
- rescue StandardError => e
220
- raise if @exception
221
-
222
- file = nil
223
- warn log_message(Logger::WARN, e, pass: true) if warning?
224
- end
204
+ begin
205
+ file &&= @workspace.home.join(env('LOG_DIR', ''), file).realdirpath
206
+ rescue StandardError => e
207
+ file = nil
208
+ warn log_message(Logger::WARN, e, pass: true) if warning?
225
209
  end
226
210
  log[:progname] ||= @name
227
211
  if (val = env('LOG_LEVEL', ignore: false))
@@ -259,6 +243,44 @@ module Squared
259
243
  end
260
244
  end
261
245
 
246
+ def <=>(other)
247
+ return 0 unless workspace == other.workspace
248
+
249
+ a, b = graph_deps
250
+ return 1 if a.include?(other)
251
+
252
+ c, d = graph_deps other
253
+ e = b.reject { |val| d.include?(val) }
254
+ f = d.reject { |val| b.include?(val) }
255
+ if parent == other.parent
256
+ g = []
257
+ h = []
258
+ else
259
+ g = a.reject { |val| c.include?(val) }
260
+ h = c.reject { |val| a.include?(val) }
261
+ end
262
+ g << self
263
+ h << other
264
+ e.concat(g)
265
+ f.concat(h)
266
+ if g.any? { |val| f.include?(val) }
267
+ -1
268
+ elsif h.any? { |val| e.include?(val) }
269
+ 1
270
+ elsif e.any? { |val| f.include?(val) }
271
+ -1
272
+ elsif f.any? { |val| e.include?(val) }
273
+ 1
274
+ elsif @index != -1 && (i = other.instance_variable_get(:@index)) != -1
275
+ @index < i ? -1 : 1
276
+ else
277
+ 0
278
+ end
279
+ rescue StandardError => e
280
+ log&.debug e
281
+ 0
282
+ end
283
+
262
284
  def ref
263
285
  Base.ref
264
286
  end
@@ -347,29 +369,22 @@ module Squared
347
369
  end
348
370
 
349
371
  def add(path, name = nil, **kwargs, &blk)
350
- checkdir = lambda do |val|
351
- if val.directory? && !val.empty?
352
- true
353
- else
354
- log&.warn "workspace \"#{val}\" (#{val.empty? ? 'empty' : 'not found'})"
355
- false
356
- end
357
- end
358
- if path.is_a?(String) && (seg = path[%r{^(.+)[\\/]\*+$}, 1])
359
- return self unless checkdir.call(path = basepath(seg))
372
+ if path.is_a?(String) && (seg = path[%r{\A(.+)[\\/]\*+\z}, 1])
373
+ return self unless checkdir?(path = basepath(seg))
360
374
 
361
- path = path.children.select { |val| checkdir.call(val) }
375
+ path = path.children.select { |val| checkdir?(val) }
362
376
  end
363
377
  if path.is_a?(Array)
364
378
  name = @name if name == true
365
379
  path.each { |val| add(val, name && task_join(name, File.basename(val)), **kwargs, &blk) }
366
380
  return self
367
- elsif !projectpath?(path = basepath(path)) || !checkdir.call(path)
381
+ elsif !projectpath?(path = basepath(path)) || !checkdir?(path)
368
382
  return self
369
- elsif name.is_a?(Symbol)
370
- name = name.to_s
371
- elsif !name.is_a?(String)
372
- name = nil
383
+ else
384
+ name = case name
385
+ when String, Symbol
386
+ name.to_s
387
+ end
373
388
  end
374
389
  if @withargs
375
390
  data = @withargs.dup
@@ -380,8 +395,14 @@ module Squared
380
395
  kwargs[:ref] = ref unless kwargs.key?(:ref)
381
396
  parent = self
382
397
  proj = nil
383
- workspace.add(path, name || path.basename, **kwargs) do
384
- variable_set :parent, parent
398
+ name = case name
399
+ when String, Symbol
400
+ name.to_s
401
+ else
402
+ path.basename
403
+ end
404
+ workspace.add(path, name, **kwargs) do
405
+ __send__ :parent_set, parent
385
406
  proj = self
386
407
  end
387
408
  @children << proj
@@ -389,6 +410,11 @@ module Squared
389
410
  self
390
411
  end
391
412
 
413
+ def chain(*args, **kwargs)
414
+ workspace.chain(*args, project: self, **kwargs)
415
+ self
416
+ end
417
+
392
418
  def inject(obj, *args, **kwargs, &blk)
393
419
  return self unless enabled?
394
420
 
@@ -408,7 +434,7 @@ module Squared
408
434
 
409
435
  run_b(@run, sync: sync, from: from) if series?(@run)
410
436
  args = @output
411
- banner = verbosetype > 1 if task_invoked?('build', 'build:sync', 'default')
437
+ banner = verbosetype > 1 if from_base?('build')
412
438
  end
413
439
  if args.first.is_a?(Struct)
414
440
  f, blk = args.first.to_a
@@ -463,13 +489,42 @@ module Squared
463
489
  cmd = compose(as_get(opts), flags, script: true, args: extra, from: from)
464
490
  from = :script if from == :run && script?
465
491
  end
466
- run(cmd, var, from: from, banner: banner, sync: sync)
492
+ run(cmd, var, sync: sync, from: from, banner: banner)
467
493
  end
468
494
 
469
495
  def depend(*, sync: invoked_sync?('depend'), **)
470
496
  run_b(@depend, sync: sync, from: :depend)
471
497
  end
472
498
 
499
+ def prereqs(*, sync: invoked_sync?('prereqs'), **)
500
+ on :first, :prereqs
501
+ graph_deps.flatten(1).sort.each do |proj|
502
+ next if @@graph[:_].include?(proj)
503
+
504
+ if (val = ENV["PREREQS_#{proj.instance_variable_get(:@envname)}"] || ENV["PREREQS_#{proj.ref.upcase}"])
505
+ val.split(/\s*,\s*/).each do |meth|
506
+ if proj.respond_to?(meth.to_sym)
507
+ begin
508
+ proj.__send__(meth, sync: sync)
509
+ rescue StandardError => e
510
+ ret = on(:error, :prereqs, e)
511
+ raise unless ret == true
512
+ else
513
+ next
514
+ end
515
+ end
516
+ if warning?
517
+ warn log_message(Logger::WARN, name, 'method not found', subject: 'prereqs', hint: meth, pass: true)
518
+ end
519
+ end
520
+ elsif proj.build?
521
+ proj.build(sync: sync)
522
+ end
523
+ @@graph[:_] << proj
524
+ end
525
+ on :last, :prereqs
526
+ end
527
+
473
528
  def archive(*, sync: invoked_sync?('archive'), **)
474
529
  return unless @archive.is_a?(Hash)
475
530
 
@@ -477,7 +532,7 @@ module Squared
477
532
  end
478
533
 
479
534
  def doc(*, sync: invoked_sync?('doc'), **)
480
- run_b(@doc, sync: sync, from: :doc)
535
+ run_b(@doc, sync: sync, from: :doc, banner: from_base?('doc') ? verbosetype > 1 : verbose)
481
536
  end
482
537
 
483
538
  def lint(*, sync: invoked_sync?('lint'), **)
@@ -518,7 +573,7 @@ module Squared
518
573
  if @clean.is_a?(Enumerable) && !series?(@clean)
519
574
  @clean.each do |val|
520
575
  entry = path + (val = val.to_s)
521
- if entry.directory? && val.match?(%r{[\\/]$})
576
+ if entry.directory? && val.match?(%r{[\\/]\z})
522
577
  log&.warn "rm -rf #{entry}"
523
578
  rm_rf(entry, verbose: verbose)
524
579
  else
@@ -541,7 +596,7 @@ module Squared
541
596
  on :last, :clean unless pass
542
597
  end
543
598
 
544
- def graph(start = [], tasks = nil, sync: invoked_sync?('graph'), pass: [], out: nil)
599
+ def graph(start = [], tasks = nil, *, sync: invoked_sync?('graph'), pass: [], out: nil, **)
545
600
  if (val = env('GRAPH', strict: true))
546
601
  tasks ||= []
547
602
  split_escape(val).each do |task|
@@ -559,21 +614,19 @@ module Squared
559
614
  data[name] << self
560
615
  on :first, :graph
561
616
  end
562
- begin
563
- done = graph_branch(self, data, tasks, out, sync: sync, pass: pass)
564
- rescue StandardError => e
565
- ret = on(:error, :graph, e)
566
- raise unless ret == true
567
- end
617
+ ret = graph_branch(self, data, tasks, out, sync: sync, pass: pass)
618
+ rescue StandardError => e
619
+ ret = on(:error, :graph, e)
620
+ raise unless ret == true
621
+ else
568
622
  if out
569
- [out, done]
623
+ [out, ret]
570
624
  else
571
625
  on :last, :graph
572
626
  end
573
627
  end
574
628
 
575
- def unpack(target, uri:, sync: true, digest: nil, ext: nil, force: false, depth: 1, headers: {},
576
- from: :unpack)
629
+ def unpack(target, uri:, sync: true, digest: nil, ext: nil, force: false, depth: 1, headers: {}, from: :unpack)
577
630
  if !target.exist?
578
631
  target.mkpath
579
632
  elsif !target.directory?
@@ -581,18 +634,11 @@ module Squared
581
634
  elsif !target.empty?
582
635
  raise_error('directory not empty', hint: target) unless force || env('UNPACK_FORCE')
583
636
  create = true
584
- elsif !uri
585
- raise_error('no download uri', hint: target)
586
637
  end
587
638
  if digest
588
639
  require 'digest'
589
- if (n = digest.index(':').to_i) > 0
590
- size = digest[0, n].downcase
591
- digest = digest[n + 1..-1]
592
- else
593
- size = digest.size
594
- end
595
- algo = case size
640
+ digest, type = digest.split(':', 2).reverse
641
+ algo = case type&.downcase || digest.size
596
642
  when 32, 'md5'
597
643
  Digest::MD5
598
644
  when 'rmd160'
@@ -610,16 +656,15 @@ module Squared
610
656
  end
611
657
  end
612
658
  if (val = env('HEADERS')) && (val = parse_json(val, hint: "HEADERS_#{@envname}"))
613
- headers = val
659
+ headers = headers.is_a?(Hash) ? headers.merge(val) : val
614
660
  end
615
661
  data = nil
616
662
  (uri = as_a(uri)).each_with_index do |url, index|
617
- last = index == uri.size - 1
618
663
  fetch_uri(url, headers) do |f|
619
664
  data = f.read
620
665
  if algo && algo.hexdigest(data) != digest
621
666
  data = nil
622
- raise_error("checksum failed: #{digest}", hint: url) if last
667
+ raise_error("checksum failed: #{digest}", hint: url) if index == uri.size - 1
623
668
  end
624
669
  next if ext && index == 0
625
670
 
@@ -632,14 +677,11 @@ module Squared
632
677
  ext = 'txz'
633
678
  end
634
679
  end
635
- if data
636
- uri = url
637
- break
638
- elsif last
639
- raise_error('no content', hint: url)
640
- end
680
+ break uri = url if data
681
+ end
682
+ unless data && (ext ||= URI.parse(uri).path[/\.(\w+)(?:\?|\z)/, 1])
683
+ raise_error("no content#{data ? ' type' : ''}", hint: uri)
641
684
  end
642
- raise_error('no content type', hint: uri) unless ext ||= URI.parse(uri).path[/\.(\w+)(\?|$)/i, 1]
643
685
  ext = ext.downcase
644
686
  if (val = env("#{%w[zip 7z gem].include?(ext) ? ext.upcase : 'TAR'}_DEPTH", ignore: false))
645
687
  depth = val.to_i
@@ -698,8 +740,8 @@ module Squared
698
740
  ensure
699
741
  if dir
700
742
  remove_entry dir
701
- elsif file
702
- file.unlink
743
+ else
744
+ file&.unlink
703
745
  end
704
746
  end
705
747
  end
@@ -747,32 +789,44 @@ module Squared
747
789
  self
748
790
  end
749
791
 
750
- def variable_set(key, *val, **kwargs, &blk)
751
- if variables.include?(key)
792
+ def variable_set(key, *args, **kwargs, &blk)
793
+ if variables.include?(key) || blocks.include?(key)
794
+ val = case args.size
795
+ when 0
796
+ nil
797
+ when 1
798
+ args.first
799
+ else
800
+ args
801
+ end
752
802
  case key
753
- when :build, :run
754
- run_set(*val, **kwargs)
803
+ when :index
804
+ index_set val
805
+ when :graph
806
+ graph_set val
807
+ when :pass
808
+ pass_set val
809
+ when :exclude
810
+ exclude_set val
811
+ when :parent
812
+ parent_set val
813
+ when :run
814
+ run_set(*args, **kwargs)
755
815
  when :script
756
- script_set(*val, **kwargs)
816
+ script_set(*args, **kwargs)
757
817
  when :env
758
- run_set(output[0], *val, **kwargs)
759
- when :parent
760
- @parent = val if (val = val.first).is_a?(Project::Base)
761
- when :graph
762
- @graph = case val.first
763
- when NilClass, FalseClass
764
- nil
765
- else
766
- val.flatten.map!(&:to_s).freeze
767
- end
818
+ run_set(output[0], *args, **kwargs)
768
819
  when :dependfile
769
- @dependfile = basepath(*val)
820
+ @dependfile = basepath(*args)
770
821
  else
771
- if blocks.include?(key) && block_given?
772
- series key, &blk
773
- else
774
- instance_variable_set(:"@#{key}", block_given? && val.empty? ? blk : val.first)
822
+ if block_given?
823
+ if blocks.include?(key)
824
+ series key, &blk
825
+ return self
826
+ end
827
+ val = block_args val, &blk
775
828
  end
829
+ instance_variable_set(:"@#{key}", val.first)
776
830
  end
777
831
  else
778
832
  log&.warn "variable_set: @#{key} (private)"
@@ -816,6 +870,15 @@ module Squared
816
870
  @graph.is_a?(Array) && !@graph.empty?
817
871
  end
818
872
 
873
+ def prereqs?
874
+ target = self
875
+ loop do
876
+ return true if target.graph?
877
+ break unless (target = target.parent)
878
+ end
879
+ false
880
+ end
881
+
819
882
  def copy?
820
883
  runnable?(@copy) || workspace.task_defined?(name, 'copy')
821
884
  end
@@ -908,7 +971,7 @@ module Squared
908
971
  puts_oe(*args, pipe: pipe)
909
972
  end
910
973
 
911
- def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, chdir: path, from: nil, **)
974
+ def run(cmd = @session, var = nil, exception: @exception, sync: true, from: nil, banner: true, chdir: path, **)
912
975
  unless cmd
913
976
  if warning?
914
977
  from &&= from.to_s
@@ -916,6 +979,7 @@ module Squared
916
979
  end
917
980
  return
918
981
  end
982
+ cmd = cmd.target if cmd.is_a?(OptionPartition)
919
983
  cmd = session_done cmd
920
984
  log&.info cmd
921
985
  on :first, from
@@ -950,7 +1014,7 @@ module Squared
950
1014
  end
951
1015
  end
952
1016
 
953
- def run_s(*cmd, env: nil, sync: true, banner: verbose != false, from: nil, **kwargs)
1017
+ def run_s(*cmd, env: nil, sync: true, from: nil, banner: verbose != false, **kwargs)
954
1018
  on :first, from
955
1019
  begin
956
1020
  cmd.flatten.each { |val| run(val, env, sync: sync, banner: banner, **kwargs) }
@@ -961,11 +1025,11 @@ module Squared
961
1025
  on :last, from
962
1026
  end
963
1027
 
964
- def run_b(obj, from: nil, sync: true)
1028
+ def run_b(obj, **kwargs)
965
1029
  case obj
966
1030
  when Struct
967
1031
  if (any = instance_eval(&obj.block) || obj.run)
968
- run_b(any, from: from, sync: sync)
1032
+ run_b(any, **kwargs)
969
1033
  end
970
1034
  when Proc
971
1035
  instance_eval(&obj)
@@ -975,9 +1039,9 @@ module Squared
975
1039
  if series?(obj)
976
1040
  obj.each(&:call)
977
1041
  elsif obj.is_a?(Array) && obj.any? { |val| !val.is_a?(String) }
978
- build(*obj, from: from, sync: sync)
1042
+ build(*obj, **kwargs)
979
1043
  elsif obj
980
- run_s(obj.is_a?(Enumerable) ? obj.to_a : obj, from: from, sync: sync)
1044
+ run_s(obj.is_a?(Enumerable) ? obj.to_a : obj, **kwargs)
981
1045
  end
982
1046
  end
983
1047
  end
@@ -1062,7 +1126,7 @@ module Squared
1062
1126
  k = 0
1063
1127
  final = data.keys.last
1064
1128
  while k < depth
1065
- indent = k > 0 && ((last && !j) || (j && k == depth - 1) || single)
1129
+ indent = k > 0 ? ((last && !j) || (j && k == depth - 1) || single) : j && last && depth == 1
1066
1130
  s += "#{indent || (last && data[final].last == context) ? ' ' : a} "
1067
1131
  k += 1
1068
1132
  end
@@ -1097,10 +1161,28 @@ module Squared
1097
1161
  deps.concat(objs)
1098
1162
  end
1099
1163
  end
1100
- data[target.name] = deps.uniq.reject { |proj| proj == target }
1164
+ deps.uniq!
1165
+ deps.delete(target)
1166
+ data[target.name] = deps
1101
1167
  data
1102
1168
  end
1103
1169
 
1170
+ def graph_deps(target = self)
1171
+ key = target.name
1172
+ return @@graph[key] if @@graph.key?(key)
1173
+
1174
+ base = []
1175
+ deps = []
1176
+ loop do
1177
+ deps.concat(graph_branch(target, graph_collect(target), []))
1178
+ break unless (target = target.parent)
1179
+
1180
+ base << target
1181
+ end
1182
+ deps.uniq!
1183
+ @@graph[key] = [base, deps]
1184
+ end
1185
+
1104
1186
  def env(key, default = nil, suffix: nil, equals: nil, ignore: nil, strict: false)
1105
1187
  a = "#{key}_#{@envname}"
1106
1188
  ret = if suffix
@@ -1152,6 +1234,12 @@ module Squared
1152
1234
  cmd.done
1153
1235
  end
1154
1236
 
1237
+ def session_arg?(*args, target: @session, **kwargs)
1238
+ return false unless target
1239
+
1240
+ OptionPartition.arg?(target, *args, **kwargs)
1241
+ end
1242
+
1155
1243
  def option(*args, target: @session, prefix: target&.first, **kwargs)
1156
1244
  if prefix
1157
1245
  args.each do |val|
@@ -1178,8 +1266,8 @@ module Squared
1178
1266
  end
1179
1267
 
1180
1268
  def print_item(*val)
1181
- puts if @@print_order > 0 && stdout?
1182
- @@print_order += 1
1269
+ puts unless printfirst?
1270
+ printsucc
1183
1271
  puts val unless val.empty? || (val.size == 1 && val.first.nil?)
1184
1272
  end
1185
1273
 
@@ -1209,10 +1297,9 @@ module Squared
1209
1297
 
1210
1298
  def print_footer(*lines, sub: nil, reverse: false, right: false, **kwargs)
1211
1299
  n = Project.max_width(lines)
1212
- sub = as_a sub
1213
1300
  lines.map! do |val|
1214
1301
  s = right ? val.rjust(n) : val.ljust(n)
1215
- sub.each { |h| s = sub_style(s, **h) }
1302
+ sub&.each { |h| s = sub_style(s, **h) }
1216
1303
  s
1217
1304
  end
1218
1305
  ret = [sub_style(ARG[:BORDER][1] * n, styles: kwargs.key?(:border) ? kwargs[:border] : borderstyle), *lines]
@@ -1241,9 +1328,9 @@ module Squared
1241
1328
  if verbose
1242
1329
  out = []
1243
1330
  if data[:command]
1244
- if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+)) /
1331
+ if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+))( |\z)/
1245
1332
  path = $3 || $2 || $1
1246
- cmd = cmd.sub(path, File.basename(path).upcase)
1333
+ cmd = cmd.sub(path, stripext(path).upcase)
1247
1334
  end
1248
1335
  out << cmd
1249
1336
  end
@@ -1502,12 +1589,12 @@ module Squared
1502
1589
  b = sub_style("#{pkg} #{ver}", styles: theme[:inline])
1503
1590
  c, d = rev == 1 || lock ? ['y/N', 'N'] : ['Y/n', 'Y']
1504
1591
  e = lock ? " #{sub_style('(locked)', styles: color(:red))}" : ''
1505
- confirm("Upgrade to #{a}? #{b + e} [#{c}] ", d)
1592
+ confirm "Upgrade to #{a}? #{b + e} [#{c}] ", d
1506
1593
  end
1507
1594
 
1508
1595
  def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil,
1509
1596
  multiple: false, force: true, **kwargs)
1510
- puts if !series && @@print_order > 0
1597
+ puts if !series && !printfirst?
1511
1598
  msg = "#{msg} (optional)" unless force
1512
1599
  unless (ret = choice(msg, list, multiple: multiple, force: force, **kwargs)) || !force
1513
1600
  raise_error 'user cancelled'
@@ -1519,7 +1606,7 @@ module Squared
1519
1606
  end
1520
1607
  ret = multiple ? ret.map! { |val| val.sub(trim, '') } : ret.sub(trim, '') if trim
1521
1608
  if column
1522
- a, b = as_a(column)
1609
+ a, b = as_a column
1523
1610
  ret = as_a(ret).map! { |val| val[a, b || 1] }
1524
1611
  ret = ret.first unless multiple
1525
1612
  end
@@ -1554,10 +1641,24 @@ module Squared
1554
1641
  ret << (val.empty? ? nil : val)
1555
1642
  end
1556
1643
  end
1557
- @@print_order += 1 unless series
1644
+ printsucc unless series
1558
1645
  ret
1559
1646
  end
1560
1647
 
1648
+ def command_args(args, force: false, **kwargs)
1649
+ return unless args.size == 1 && !option('i', 'interactive', **kwargs, equals: '0')
1650
+
1651
+ readline('Enter arguments', force: force)
1652
+ end
1653
+
1654
+ def block_args(val = nil, &blk)
1655
+ if (ret = instance_eval(&blk)).nil?
1656
+ val
1657
+ else
1658
+ ret.is_a?(Array) ? ret : [ret]
1659
+ end
1660
+ end
1661
+
1561
1662
  def runenv
1562
1663
  nil
1563
1664
  end
@@ -1608,15 +1709,17 @@ module Squared
1608
1709
  end
1609
1710
 
1610
1711
  def indexitem(val)
1611
- return unless val =~ /\A\^(\d+)(:.+)?\z/
1612
-
1613
- [$1.to_i, $2 && $2[1..-1]]
1712
+ [$1.to_i, $2 && $2[1..-1]] if val =~ /\A\^(\d+)(:.+)?\z/
1614
1713
  end
1615
1714
 
1616
1715
  def indexerror(val, list = nil)
1617
1716
  raise_error("requested index #{val}", hint: list && "of #{list.size}")
1618
1717
  end
1619
1718
 
1719
+ def printsucc
1720
+ @@print_order += 1
1721
+ end
1722
+
1620
1723
  def color(val)
1621
1724
  ret = theme[val]
1622
1725
  ret && !ret.empty? ? ret : [val]
@@ -1642,16 +1745,14 @@ module Squared
1642
1745
  end
1643
1746
 
1644
1747
  def on(event, from, *args, **kwargs)
1645
- return unless from
1646
-
1647
- @events[event][from]&.each do |obj|
1648
- if obj.is_a?(Array) && obj[1].is_a?(Hash)
1649
- opts = kwargs.empty? ? obj[1] : obj[1].merge(kwargs)
1650
- target = obj[0]
1651
- else
1652
- opts = kwargs
1653
- target = obj
1654
- end
1748
+ return unless from && (data = @events[event])
1749
+
1750
+ data[from]&.each do |obj|
1751
+ target, opts = if obj.is_a?(Array) && obj[1].is_a?(Hash)
1752
+ [obj[0], kwargs.empty? ? obj[1] : obj[1].merge(kwargs)]
1753
+ else
1754
+ [obj, kwargs]
1755
+ end
1655
1756
  as_a(target, flat: true).each do |cmd|
1656
1757
  case cmd
1657
1758
  when Proc, Method
@@ -1760,6 +1861,63 @@ module Squared
1760
1861
  @output[4] = args unless @output[4] == false || args.nil?
1761
1862
  end
1762
1863
 
1864
+ def index_set(val)
1865
+ @index = val if val.is_a?(Numeric)
1866
+ end
1867
+
1868
+ def parent_set(val)
1869
+ @parent = val if val.is_a?(Project::Base)
1870
+ end
1871
+
1872
+ def exception_set(val)
1873
+ @exception = env_bool(val, workspace.exception, strict: true)
1874
+ end
1875
+
1876
+ def pipe_set(val)
1877
+ @pipe = env_pipe(val, workspace.pipe, strict: true)
1878
+ end
1879
+
1880
+ def verbose_set(val)
1881
+ @verbose = case val
1882
+ when NilClass
1883
+ workspace.verbose
1884
+ when String
1885
+ env_bool(val, workspace.verbose, strict: true, index: true)
1886
+ else
1887
+ val
1888
+ end
1889
+ end
1890
+
1891
+ def theme_set(common)
1892
+ @theme = if !verbose
1893
+ {}
1894
+ elsif common
1895
+ workspace.theme
1896
+ else
1897
+ __get__(:theme)[:project][to_sym] ||= {}
1898
+ end
1899
+ end
1900
+
1901
+ def graph_set(val)
1902
+ @graph = if val
1903
+ as_a(val).map { |s| workspace.prefix ? workspace.task_name(s).to_sym : s.to_sym }.freeze
1904
+ end
1905
+ end
1906
+
1907
+ def pass_set(val)
1908
+ @pass = (val ? as_a(val, :to_s) : []).freeze
1909
+ end
1910
+
1911
+ def exclude_set(val)
1912
+ @exclude = (val ? as_a(val, :to_sym) : []).freeze
1913
+ end
1914
+
1915
+ def dependfile_set(list)
1916
+ @dependindex = list.index { |file| basepath(file).exist? }.tap do |index|
1917
+ @dependfile = @path + list[index || 0]
1918
+ end
1919
+ end
1920
+
1763
1921
  def as_get(val)
1764
1922
  return unless val
1765
1923
 
@@ -1792,10 +1950,23 @@ module Squared
1792
1950
  val.absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?(File.join('..', ''))
1793
1951
  end
1794
1952
 
1953
+ def checkdir?(val)
1954
+ if val.directory? && !val.empty?
1955
+ true
1956
+ else
1957
+ log&.warn "directory \"#{val}\" (#{val.empty? ? 'empty' : 'not found'})"
1958
+ false
1959
+ end
1960
+ end
1961
+
1795
1962
  def semmajor?(cur, want)
1796
1963
  (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
1797
1964
  end
1798
1965
 
1966
+ def printfirst?
1967
+ @@print_order == 0
1968
+ end
1969
+
1799
1970
  def runnable?(val)
1800
1971
  case val
1801
1972
  when String, Enumerable, Proc, Method
@@ -1809,8 +1980,8 @@ module Squared
1809
1980
  val.is_a?(Array) && val.all? { |p| p.is_a?(Proc) || p.is_a?(Method) }
1810
1981
  end
1811
1982
 
1812
- def session_arg?(*args, target: @session, **kwargs)
1813
- !!target && OptionPartition.arg?(target, *args, **kwargs)
1983
+ def from_base?(val)
1984
+ task_invoked?(val, "#{val}:sync", 'default')
1814
1985
  end
1815
1986
 
1816
1987
  def from_sync?(*val)
@@ -1825,13 +1996,11 @@ module Squared
1825
1996
  return true if val || from_sync?(ac = workspace.task_name(action))
1826
1997
  return val if group && !(val = from_sync?(ac, group)).nil?
1827
1998
  return val if (base = workspace.find_base(self)) && !(val = from_sync?(ac, base.ref)).nil?
1999
+ return false if workspace.series.chain?(val = task_join(name, action))
2000
+ return true if task_invoked?(val) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
1828
2001
 
1829
- if task_invoked?(task_join(name, ac)) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
1830
- true
1831
- else
1832
- val = workspace.series.name_get(action)
1833
- val != action && invoked_sync?(val)
1834
- end
2002
+ val = workspace.series.name_get(action)
2003
+ val != action && invoked_sync?(val)
1835
2004
  end
1836
2005
 
1837
2006
  def success?(ret)
@@ -1854,22 +2023,19 @@ module Squared
1854
2023
  workspace.warning
1855
2024
  end
1856
2025
 
1857
- def has_value?(data, val)
1858
- return data.value?(val) if data.is_a?(Hash)
1859
- return data.is_a?(Enumerable) && data.to_a.include?(val) if !val.is_a?(Enumerable) || val.is_a?(Hash)
1860
-
1861
- val.to_a.any? do |obj|
1862
- case data
1863
- when Hash
1864
- data.value?(obj)
1865
- when Enumerable
1866
- data.to_a.include?(obj)
1867
- end
2026
+ def has_value?(data, other)
2027
+ case data
2028
+ when Hash
2029
+ other.is_a?(Enumerable) ? other.any? { |obj| data.value?(obj) } : data.value?(other)
2030
+ when Enumerable
2031
+ other.is_a?(Enumerable) ? other.any? { |obj| data.include?(obj) } : data.include?(other)
2032
+ else
2033
+ false
1868
2034
  end
1869
2035
  end
1870
2036
 
1871
2037
  def variables
1872
- Base.tasks + VAR_SET
2038
+ VAR_SET
1873
2039
  end
1874
2040
 
1875
2041
  def blocks