squared 0.4.12 → 0.4.14

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, first: {}, last: {}, error: {}, common: ARG[:COMMON],
80
+ **kwargs)
78
81
  @path = path
79
82
  @workspace = workspace
80
83
  @name = name.to_s.freeze
@@ -87,48 +90,29 @@ module Squared
87
90
  @copy = kwargs[:copy]
88
91
  @clean = kwargs[:clean]
89
92
  @version = kwargs[:version]
90
- @archive = case archive
91
- when String, Array
92
- { uri: archive }
93
- when Hash
94
- archive
95
- end
96
- @release = release
97
- @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
93
+ @release = kwargs[:release]
115
94
  @output = []
116
95
  @ref = []
117
96
  @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
97
  @events = {
124
98
  first: first,
125
99
  last: last,
126
100
  error: error
127
101
  }
102
+ @envname = env_key(@name).freeze
128
103
  @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
129
104
  @parent = nil
130
105
  @global = false
106
+ @index = -1
131
107
  run_set(kwargs[:run], kwargs[:env], opts: kwargs.fetch(:opts, true))
108
+ exception_set kwargs[:exception]
109
+ pipe_set kwargs[:pipe]
110
+ verbose_set kwargs[:verbose]
111
+ graph_set kwargs[:graph]
112
+ pass_set kwargs[:pass]
113
+ exclude_set kwargs[:exclude]
114
+ archive_set kwargs[:archive]
115
+ theme_set common
132
116
  initialize_ref Base.ref
133
117
  end
134
118
 
@@ -216,7 +200,7 @@ module Squared
216
200
  file &&= @workspace.home.join(env('LOG_DIR', ''), file).realdirpath
217
201
  rescue StandardError => e
218
202
  file = nil
219
- warn log_message(Logger::WARN, e, pass: true) if warning?
203
+ warn log_message(Logger::WARN, e) if warning?
220
204
  end
221
205
  log[:progname] ||= @name
222
206
  if (val = env('LOG_LEVEL', ignore: false))
@@ -254,6 +238,44 @@ module Squared
254
238
  end
255
239
  end
256
240
 
241
+ def <=>(other)
242
+ return 0 unless workspace == other.workspace
243
+
244
+ a, b = graph_deps
245
+ return 1 if a.include?(other)
246
+
247
+ c, d = graph_deps other
248
+ e = b - d
249
+ f = d - b
250
+ if parent == other.parent
251
+ g = []
252
+ h = []
253
+ else
254
+ g = a - c
255
+ h = c - a
256
+ end
257
+ g << self
258
+ h << other
259
+ e.concat(g)
260
+ f.concat(h)
261
+ if g.any? { |val| f.include?(val) }
262
+ -1
263
+ elsif h.any? { |val| e.include?(val) }
264
+ 1
265
+ elsif e.any? { |val| f.include?(val) }
266
+ -1
267
+ elsif f.any? { |val| e.include?(val) }
268
+ 1
269
+ elsif @index != -1 && (i = other.instance_variable_get(:@index)) != -1
270
+ @index < i ? -1 : 1
271
+ else
272
+ 0
273
+ end
274
+ rescue StandardError => e
275
+ log&.debug e
276
+ 0
277
+ end
278
+
257
279
  def ref
258
280
  Base.ref
259
281
  end
@@ -342,24 +364,16 @@ module Squared
342
364
  end
343
365
 
344
366
  def add(path, name = nil, **kwargs, &blk)
345
- checkdir = lambda do |val|
346
- if val.directory? && !val.empty?
347
- true
348
- else
349
- log&.warn "workspace \"#{val}\" (#{val.empty? ? 'empty' : 'not found'})"
350
- false
351
- end
352
- end
353
367
  if path.is_a?(String) && (seg = path[%r{\A(.+)[\\/]\*+\z}, 1])
354
- return self unless checkdir.call(path = basepath(seg))
368
+ return self unless checkdir?(path = basepath(seg))
355
369
 
356
- path = path.children.select { |val| checkdir.call(val) }
370
+ path = path.children.select { |val| checkdir?(val) }
357
371
  end
358
372
  if path.is_a?(Array)
359
373
  name = @name if name == true
360
374
  path.each { |val| add(val, name && task_join(name, File.basename(val)), **kwargs, &blk) }
361
375
  return self
362
- elsif !projectpath?(path = basepath(path)) || !checkdir.call(path)
376
+ elsif !projectpath?(path = basepath(path)) || !checkdir?(path)
363
377
  return self
364
378
  else
365
379
  name = case name
@@ -376,8 +390,14 @@ module Squared
376
390
  kwargs[:ref] = ref unless kwargs.key?(:ref)
377
391
  parent = self
378
392
  proj = nil
379
- workspace.add(path, name || path.basename, **kwargs) do
380
- variable_set :parent, parent
393
+ name = case name
394
+ when String, Symbol
395
+ name.to_s
396
+ else
397
+ path.basename
398
+ end
399
+ workspace.add(path, name, **kwargs) do
400
+ __send__ :parent_set, parent
381
401
  proj = self
382
402
  end
383
403
  @children << proj
@@ -395,7 +415,7 @@ module Squared
395
415
 
396
416
  out = obj.link(self, *args, **kwargs, &blk) if obj.respond_to?(:link)
397
417
  if !out
398
- warn log_message(Logger::WARN, 'link not compatible', subject: obj.to_s, hint: name, pass: true)
418
+ warn log_message(Logger::WARN, 'link not compatible', subject: obj.to_s, hint: name)
399
419
  elsif out.respond_to?(:build)
400
420
  out.build
401
421
  end
@@ -409,7 +429,7 @@ module Squared
409
429
 
410
430
  run_b(@run, sync: sync, from: from) if series?(@run)
411
431
  args = @output
412
- banner = verbosetype > 1 if task_invoked?('build', 'build:sync', 'default')
432
+ banner = verbosetype > 1 if from_base?('build')
413
433
  end
414
434
  if args.first.is_a?(Struct)
415
435
  f, blk = args.first.to_a
@@ -450,9 +470,9 @@ module Squared
450
470
  case opts
451
471
  when Hash
452
472
  opts = append_hash(opts, build: true)
453
- cmd = as_a(cmd).push(flags).concat(opts).compact.join(' ')
473
+ cmd = Array(cmd).push(flags).concat(opts).compact.join(' ')
454
474
  when Enumerable
455
- cmd = as_a(cmd).concat(opts.to_a)
475
+ cmd = Array(cmd).concat(opts.to_a)
456
476
  cmd.map! { |val| "#{val} #{flags}" } if flags
457
477
  cmd = cmd.join(' && ')
458
478
  else
@@ -464,13 +484,40 @@ module Squared
464
484
  cmd = compose(as_get(opts), flags, script: true, args: extra, from: from)
465
485
  from = :script if from == :run && script?
466
486
  end
467
- run(cmd, var, from: from, banner: banner, sync: sync)
487
+ run(cmd, var, sync: sync, from: from, banner: banner)
468
488
  end
469
489
 
470
490
  def depend(*, sync: invoked_sync?('depend'), **)
471
491
  run_b(@depend, sync: sync, from: :depend)
472
492
  end
473
493
 
494
+ def prereqs(*, sync: invoked_sync?('prereqs'), **)
495
+ on :first, :prereqs
496
+ graph_deps.flatten(1).sort.each do |proj|
497
+ next if @@graph[:_].include?(proj)
498
+
499
+ if (val = ENV["PREREQS_#{proj.instance_variable_get(:@envname)}"] || ENV["PREREQS_#{proj.ref.upcase}"])
500
+ val.split(/\s*,\s*/).each do |meth|
501
+ if proj.respond_to?(meth.to_sym)
502
+ begin
503
+ proj.__send__(meth, sync: sync)
504
+ rescue StandardError => e
505
+ ret = on(:error, :prereqs, e)
506
+ raise unless ret == true
507
+ else
508
+ next
509
+ end
510
+ end
511
+ warn log_message(Logger::WARN, name, 'method not found', subject: 'prereqs', hint: meth)
512
+ end
513
+ elsif proj.build?
514
+ proj.build(sync: sync)
515
+ end
516
+ @@graph[:_] << proj
517
+ end
518
+ on :last, :prereqs
519
+ end
520
+
474
521
  def archive(*, sync: invoked_sync?('archive'), **)
475
522
  return unless @archive.is_a?(Hash)
476
523
 
@@ -478,7 +525,7 @@ module Squared
478
525
  end
479
526
 
480
527
  def doc(*, sync: invoked_sync?('doc'), **)
481
- run_b(@doc, sync: sync, from: :doc)
528
+ run_b(@doc, sync: sync, from: :doc, banner: from_base?('doc') ? verbosetype > 1 : verbose)
482
529
  end
483
530
 
484
531
  def lint(*, sync: invoked_sync?('lint'), **)
@@ -542,7 +589,7 @@ module Squared
542
589
  on :last, :clean unless pass
543
590
  end
544
591
 
545
- def graph(start = [], tasks = nil, sync: invoked_sync?('graph'), pass: [], out: nil)
592
+ def graph(start = [], tasks = nil, *, sync: invoked_sync?('graph'), pass: [], out: nil, **)
546
593
  if (val = env('GRAPH', strict: true))
547
594
  tasks ||= []
548
595
  split_escape(val).each do |task|
@@ -560,14 +607,13 @@ module Squared
560
607
  data[name] << self
561
608
  on :first, :graph
562
609
  end
563
- begin
564
- done = graph_branch(self, data, tasks, out, sync: sync, pass: pass)
565
- rescue StandardError => e
566
- ret = on(:error, :graph, e)
567
- raise unless ret == true
568
- end
610
+ ret = graph_branch(self, data, tasks, out, sync: sync, pass: pass)
611
+ rescue StandardError => e
612
+ ret = on(:error, :graph, e)
613
+ raise unless ret == true
614
+ else
569
615
  if out
570
- [out, done]
616
+ [out, ret]
571
617
  else
572
618
  on :last, :graph
573
619
  end
@@ -606,7 +652,7 @@ module Squared
606
652
  headers = headers.is_a?(Hash) ? headers.merge(val) : val
607
653
  end
608
654
  data = nil
609
- (uri = as_a(uri)).each_with_index do |url, index|
655
+ (uri = Array(uri)).each_with_index do |url, index|
610
656
  fetch_uri(url, headers) do |f|
611
657
  data = f.read
612
658
  if algo && algo.hexdigest(data) != digest
@@ -644,7 +690,7 @@ module Squared
644
690
  file.write(data)
645
691
  file.close
646
692
  if create
647
- warn log_message(Logger::WARN, 'force remove', subject: name, hint: target, pass: true)
693
+ warn log_message(Logger::WARN, 'force remove', subject: name, hint: target)
648
694
  target.rmtree
649
695
  target.mkpath
650
696
  end
@@ -736,32 +782,46 @@ module Squared
736
782
  self
737
783
  end
738
784
 
739
- def variable_set(key, *val, **kwargs, &blk)
740
- if variables.include?(key)
785
+ def variable_set(key, *args, **kwargs, &blk)
786
+ if variables.include?(key) || blocks.include?(key)
787
+ val = case args.size
788
+ when 0
789
+ nil
790
+ when 1
791
+ args.first
792
+ else
793
+ args
794
+ end
741
795
  case key
742
- when :build, :run
743
- run_set(*val, **kwargs)
796
+ when :index
797
+ index_set val
798
+ when :graph
799
+ graph_set val
800
+ when :pass
801
+ pass_set val
802
+ when :exclude
803
+ exclude_set val
804
+ when :parent
805
+ parent_set val
806
+ when :archive
807
+ archive_set val
808
+ when :run
809
+ run_set(*args, **kwargs)
744
810
  when :script
745
- script_set(*val, **kwargs)
811
+ script_set(*args, **kwargs)
746
812
  when :env
747
- run_set(output[0], *val, **kwargs)
748
- when :parent
749
- @parent = val if (val = val.first).is_a?(Project::Base)
750
- when :graph
751
- @graph = case val.first
752
- when NilClass, FalseClass
753
- nil
754
- else
755
- val.flatten.map!(&:to_s).freeze
756
- end
813
+ run_set(output[0], *args, **kwargs)
757
814
  when :dependfile
758
- @dependfile = basepath(*val)
815
+ @dependfile = basepath(*args)
759
816
  else
760
- if blocks.include?(key) && block_given?
761
- series key, &blk
762
- else
763
- instance_variable_set(:"@#{key}", block_given? && val.empty? ? blk : val.first)
817
+ if block_given?
818
+ if blocks.include?(key)
819
+ series key, &blk
820
+ return self
821
+ end
822
+ val = block_args val, &blk
764
823
  end
824
+ instance_variable_set(:"@#{key}", val.first)
765
825
  end
766
826
  else
767
827
  log&.warn "variable_set: @#{key} (private)"
@@ -805,6 +865,15 @@ module Squared
805
865
  @graph.is_a?(Array) && !@graph.empty?
806
866
  end
807
867
 
868
+ def prereqs?
869
+ target = self
870
+ loop do
871
+ return true if target.graph?
872
+ break unless (target = target.parent)
873
+ end
874
+ false
875
+ end
876
+
808
877
  def copy?
809
878
  runnable?(@copy) || workspace.task_defined?(name, 'copy')
810
879
  end
@@ -897,16 +966,28 @@ module Squared
897
966
  puts_oe(*args, pipe: pipe)
898
967
  end
899
968
 
900
- def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, chdir: path, from: nil, **)
969
+ def run(cmd = @session, var = nil, exception: @exception, sync: true, from: nil, banner: true, chdir: path,
970
+ interactive: nil, **)
901
971
  unless cmd
902
- if warning?
903
- from &&= from.to_s
904
- warn log_message(Logger::WARN, from || 'unknown', subject: project, hint: 'no command given', pass: true)
905
- end
906
- return
972
+ from = from&.to_s || 'unknown'
973
+ return warn log_message(Logger::WARN, 'no command given', subject: project, hint: from, pass: true)
907
974
  end
975
+ i = interactive && !(@session && option('y'))
908
976
  cmd = cmd.target if cmd.is_a?(OptionPartition)
909
977
  cmd = session_done cmd
978
+ if i
979
+ title, y = case interactive
980
+ when Array
981
+ interactive
982
+ when String
983
+ [interactive, 'N']
984
+ else
985
+ ['Run', 'Y']
986
+ end
987
+ unless confirm("#{title}? [#{sub_style(cmd, styles: theme[:inline])}] #{y == 'Y' ? '[Y/n]' : '[y/N]'} ", y)
988
+ raise_error('user cancelled', hint: from)
989
+ end
990
+ end
910
991
  log&.info cmd
911
992
  on :first, from
912
993
  begin
@@ -940,7 +1021,7 @@ module Squared
940
1021
  end
941
1022
  end
942
1023
 
943
- def run_s(*cmd, env: nil, sync: true, banner: verbose != false, from: nil, **kwargs)
1024
+ def run_s(*cmd, env: nil, sync: true, from: nil, banner: verbose != false, **kwargs)
944
1025
  on :first, from
945
1026
  begin
946
1027
  cmd.flatten.each { |val| run(val, env, sync: sync, banner: banner, **kwargs) }
@@ -951,11 +1032,11 @@ module Squared
951
1032
  on :last, from
952
1033
  end
953
1034
 
954
- def run_b(obj, from: nil, sync: true)
1035
+ def run_b(obj, **kwargs)
955
1036
  case obj
956
1037
  when Struct
957
1038
  if (any = instance_eval(&obj.block) || obj.run)
958
- run_b(any, from: from, sync: sync)
1039
+ run_b(any, **kwargs)
959
1040
  end
960
1041
  when Proc
961
1042
  instance_eval(&obj)
@@ -965,9 +1046,9 @@ module Squared
965
1046
  if series?(obj)
966
1047
  obj.each(&:call)
967
1048
  elsif obj.is_a?(Array) && obj.any? { |val| !val.is_a?(String) }
968
- build(*obj, from: from, sync: sync)
1049
+ build(*obj, **kwargs)
969
1050
  elsif obj
970
- run_s(obj.is_a?(Enumerable) ? obj.to_a : obj, from: from, sync: sync)
1051
+ run_s(*Array(obj), **kwargs)
971
1052
  end
972
1053
  end
973
1054
  end
@@ -1014,7 +1095,7 @@ module Squared
1014
1095
 
1015
1096
  t = dedupe.call(proj.name)
1016
1097
  j = if out
1017
- if i == items.size - 1 || check.call(post = items[i + 1..-1]).empty?
1098
+ if i == items.size - 1 || check.call(post = items[(i + 1)..-1]).empty?
1018
1099
  true
1019
1100
  elsif !t.empty? && depth > 0
1020
1101
  post.reject { |pr| t.include?(pr) }.empty?
@@ -1026,9 +1107,9 @@ module Squared
1026
1107
  end
1027
1108
  if !out
1028
1109
  if !tasks && (script = workspace.script_get(:graph, group: proj.group, ref: proj.allref))
1029
- group = script[:graph]
1110
+ tasks = script[:graph]
1030
1111
  end
1031
- (tasks || group || (dev? ? ['build', 'copy'] : ['depend', 'build'])).each do |meth|
1112
+ (tasks || (dev? ? ['build', 'copy'] : ['depend', 'build'])).each do |meth|
1032
1113
  next if pass.include?(meth)
1033
1114
 
1034
1115
  if workspace.task_defined?(cmd = task_join(proj.name, meth))
@@ -1039,7 +1120,7 @@ module Squared
1039
1120
  end
1040
1121
  run(cmd, sync: false, banner: false)
1041
1122
  ENV.delete(key) if key
1042
- elsif proj.has?(meth, tasks || group ? nil : workspace.baseref)
1123
+ elsif proj.has?(meth, tasks ? nil : workspace.baseref)
1043
1124
  proj.__send__(meth.to_sym, sync: sync)
1044
1125
  end
1045
1126
  end
@@ -1052,7 +1133,7 @@ module Squared
1052
1133
  k = 0
1053
1134
  final = data.keys.last
1054
1135
  while k < depth
1055
- indent = k > 0 && ((last && !j) || (j && k == depth - 1) || single)
1136
+ indent = k > 0 ? ((last && !j) || (j && k == depth - 1) || single) : j && last && depth == 1
1056
1137
  s += "#{indent || (last && data[final].last == context) ? ' ' : a} "
1057
1138
  k += 1
1058
1139
  end
@@ -1076,7 +1157,6 @@ module Squared
1076
1157
  else
1077
1158
  items = workspace.find(group: val, ref: val.to_sym)
1078
1159
  end
1079
-
1080
1160
  items.each do |proj|
1081
1161
  next if pass.include?(proj.name)
1082
1162
 
@@ -1087,10 +1167,28 @@ module Squared
1087
1167
  deps.concat(objs)
1088
1168
  end
1089
1169
  end
1090
- data[target.name] = deps.uniq.reject { |proj| proj == target }
1170
+ deps.uniq!
1171
+ deps.delete(target)
1172
+ data[target.name] = deps
1091
1173
  data
1092
1174
  end
1093
1175
 
1176
+ def graph_deps(target = self)
1177
+ key = target.name
1178
+ return @@graph[key] if @@graph.key?(key)
1179
+
1180
+ base = []
1181
+ deps = []
1182
+ loop do
1183
+ deps.concat(graph_branch(target, graph_collect(target), []))
1184
+ break unless (target = target.parent)
1185
+
1186
+ base << target
1187
+ end
1188
+ deps.uniq!
1189
+ @@graph[key] = [base, deps]
1190
+ end
1191
+
1094
1192
  def env(key, default = nil, suffix: nil, equals: nil, ignore: nil, strict: false)
1095
1193
  a = "#{key}_#{@envname}"
1096
1194
  ret = if suffix
@@ -1103,7 +1201,7 @@ module Squared
1103
1201
  end
1104
1202
  return ret == equals.to_s unless equals.nil?
1105
1203
 
1106
- ret.empty? || (ignore && as_a(ignore).any? { |val| ret == val.to_s }) ? default : ret
1204
+ ret.empty? || (ignore && Array(ignore).any? { |val| ret == val.to_s }) ? default : ret
1107
1205
  end
1108
1206
 
1109
1207
  def session(*cmd, prefix: cmd.first, main: true, path: true, options: true)
@@ -1142,10 +1240,16 @@ module Squared
1142
1240
  cmd.done
1143
1241
  end
1144
1242
 
1243
+ def session_arg?(*args, target: @session, **kwargs)
1244
+ return false unless target
1245
+
1246
+ OptionPartition.arg?(target, *args, **kwargs)
1247
+ end
1248
+
1145
1249
  def option(*args, target: @session, prefix: target&.first, **kwargs)
1146
1250
  if prefix
1147
1251
  args.each do |val|
1148
- ret = env("#{stripext(prefix)}_#{val.gsub(/\W/, '_')}".upcase, **kwargs)
1252
+ ret = env(env_key(stripext(prefix), val), **kwargs)
1149
1253
  return ret if ret
1150
1254
  end
1151
1255
  end
@@ -1168,7 +1272,7 @@ module Squared
1168
1272
  end
1169
1273
 
1170
1274
  def print_item(*val)
1171
- puts if !printfirst? && stdout?
1275
+ puts unless printfirst?
1172
1276
  printsucc
1173
1277
  puts val unless val.empty? || (val.size == 1 && val.first.nil?)
1174
1278
  end
@@ -1209,6 +1313,18 @@ module Squared
1209
1313
  ret.join("\n")
1210
1314
  end
1211
1315
 
1316
+ def print_status(*args, from: nil)
1317
+ return if stdin?
1318
+
1319
+ case from
1320
+ when :outdated
1321
+ out = print_footer("major #{args[0]} / minor #{args[1]} / patch #{args[2]}", right: true).split("\n")
1322
+ out[1] = sub_style(out[1], pat: /^( +major )(\d+)(.+)$/, styles: theme[:major], index: 2)
1323
+ out[1] = sub_style(out[1], pat: /^(.+)(minor )(\d+)(.+)$/, styles: theme[:active], index: 3)
1324
+ puts out
1325
+ end
1326
+ end
1327
+
1212
1328
  def format_desc(action, flag, opts = nil, **kwargs)
1213
1329
  return unless TASK_METADATA
1214
1330
 
@@ -1230,7 +1346,7 @@ module Squared
1230
1346
  if verbose
1231
1347
  out = []
1232
1348
  if data[:command]
1233
- if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+)) /
1349
+ if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+))( |\z)/
1234
1350
  path = $3 || $2 || $1
1235
1351
  cmd = cmd.sub(path, stripext(path).upcase)
1236
1352
  end
@@ -1282,7 +1398,7 @@ module Squared
1282
1398
  out << ''
1283
1399
  end
1284
1400
  if from
1285
- out << from
1401
+ out << (from = from.to_s)
1286
1402
  pat = /\A(#{Regexp.escape(from)})(.*)\z/
1287
1403
  end
1288
1404
  else
@@ -1451,7 +1567,7 @@ module Squared
1451
1567
  raise_error("invalid JSON #{kind.name}", val, hint: hint) if kind && !ret.is_a?(kind)
1452
1568
  rescue StandardError => e
1453
1569
  log&.warn e
1454
- warn log_message(Logger::WARN, e, subject: name, pass: true) if warning?
1570
+ warn log_message(Logger::WARN, e, subject: name) if warning?
1455
1571
  else
1456
1572
  ret
1457
1573
  end
@@ -1508,25 +1624,28 @@ module Squared
1508
1624
  end
1509
1625
  ret = multiple ? ret.map! { |val| val.sub(trim, '') } : ret.sub(trim, '') if trim
1510
1626
  if column
1511
- a, b = as_a column
1512
- ret = as_a(ret).map! { |val| val[a, b || 1] }
1627
+ a, b = Array(column)
1628
+ ret = Array(ret).map! { |val| val[a, b || 1] }
1513
1629
  ret = ret.first unless multiple
1514
1630
  end
1515
1631
  if accept
1516
- a = as_a(ret).map { |val| sub_style(val, styles: theme[:inline]) }.join(', ')
1517
- accept = as_a(accept).map { |val| as_a(val) }
1632
+ hint = Array(ret).map { |val| sub_style(val, styles: theme[:inline]) }.join(', ')
1633
+ accept = Array(accept).map { |val| Array(val) }
1518
1634
  if accept.any? { |val| val[1] == true }
1519
1635
  ret = [ret]
1520
1636
  multiple = -1
1521
1637
  end
1522
1638
  loop do
1523
- c = confirm("#{accept.first[0]}#{a ? " [#{a}]" : ''} [y/N] ", 'N', timeout: 60)
1524
- if accept.shift[1] == true
1639
+ item = accept.first
1640
+ d, e = item[2] ? ['Y', '[Y/n]'] : ['N', '[y/N]']
1641
+ c = confirm("#{item[0]}#{a ? " [#{a}]" : ''} #{e} ", d, timeout: 60)
1642
+ if item[1] == true
1525
1643
  ret << c
1526
1644
  elsif !c
1527
1645
  break
1528
1646
  end
1529
- a = nil
1647
+ hint = nil
1648
+ accept.shift
1530
1649
  break if accept.empty?
1531
1650
  end
1532
1651
  exit 1 unless accept.empty?
@@ -1547,6 +1666,20 @@ module Squared
1547
1666
  ret
1548
1667
  end
1549
1668
 
1669
+ def command_args(args, force: false, **kwargs)
1670
+ return unless args.size == 1 && !option('i', 'interactive', **kwargs, equals: '0')
1671
+
1672
+ readline('Enter arguments', force: force)
1673
+ end
1674
+
1675
+ def block_args(val = nil, &blk)
1676
+ if (ret = instance_eval(&blk)).nil?
1677
+ val
1678
+ else
1679
+ Array(ret)
1680
+ end
1681
+ end
1682
+
1550
1683
  def runenv
1551
1684
  nil
1552
1685
  end
@@ -1597,13 +1730,17 @@ module Squared
1597
1730
  end
1598
1731
 
1599
1732
  def indexitem(val)
1600
- [$1.to_i, $2 && $2[1..-1]] if val =~ /\A\^(\d+)(:.+)?\z/
1733
+ [$1.to_i, $2 && $2[1..-1]] if val =~ /\A[=^#{indexchar}](\d+)(:.+)?\z/
1601
1734
  end
1602
1735
 
1603
1736
  def indexerror(val, list = nil)
1604
1737
  raise_error("requested index #{val}", hint: list && "of #{list.size}")
1605
1738
  end
1606
1739
 
1740
+ def indexchar
1741
+ workspace.windows? ? '=' : '^'
1742
+ end
1743
+
1607
1744
  def printsucc
1608
1745
  @@print_order += 1
1609
1746
  end
@@ -1656,7 +1793,8 @@ module Squared
1656
1793
  pwd = Pathname.pwd
1657
1794
  if block_given?
1658
1795
  begin
1659
- if path == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join <= RUBY_VERSION)
1796
+ pass = semscan(pass).join <= RUBY_VERSION if pass.is_a?(String)
1797
+ if (path == pwd || pass == true) && !workspace.jruby_win?
1660
1798
  ret = yield
1661
1799
  else
1662
1800
  Dir.chdir(path)
@@ -1749,6 +1887,72 @@ module Squared
1749
1887
  @output[4] = args unless @output[4] == false || args.nil?
1750
1888
  end
1751
1889
 
1890
+ def index_set(val)
1891
+ @index = val if val.is_a?(Numeric)
1892
+ end
1893
+
1894
+ def parent_set(val)
1895
+ @parent = val if val.is_a?(Project::Base)
1896
+ end
1897
+
1898
+ def exception_set(val)
1899
+ @exception = env_bool(val, workspace.exception, strict: true)
1900
+ end
1901
+
1902
+ def pipe_set(val)
1903
+ @pipe = env_pipe(val, workspace.pipe, strict: true)
1904
+ end
1905
+
1906
+ def verbose_set(val)
1907
+ @verbose = case val
1908
+ when NilClass
1909
+ workspace.verbose
1910
+ when String
1911
+ env_bool(val, workspace.verbose, strict: true, index: true)
1912
+ else
1913
+ val
1914
+ end
1915
+ end
1916
+
1917
+ def graph_set(val)
1918
+ @graph = if val
1919
+ Array(val).map { |s| workspace.prefix ? workspace.task_name(s).to_sym : s.to_sym }.freeze
1920
+ end
1921
+ end
1922
+
1923
+ def pass_set(val)
1924
+ @pass = (val ? as_a(val, :to_s) : []).freeze
1925
+ end
1926
+
1927
+ def exclude_set(val)
1928
+ @exclude = (val ? as_a(val, :to_sym) : []).freeze
1929
+ end
1930
+
1931
+ def archive_set(val)
1932
+ @archive = case val
1933
+ when String, Array
1934
+ { uri: val }
1935
+ when Hash
1936
+ val
1937
+ end
1938
+ end
1939
+
1940
+ def theme_set(common)
1941
+ @theme = if !verbose
1942
+ {}
1943
+ elsif common
1944
+ workspace.theme
1945
+ else
1946
+ __get__(:theme)[:project][to_sym] ||= {}
1947
+ end
1948
+ end
1949
+
1950
+ def dependfile_set(list)
1951
+ @dependindex = list.index { |file| basepath(file).exist? }.tap do |index|
1952
+ @dependfile = @path + list[index || 0]
1953
+ end
1954
+ end
1955
+
1752
1956
  def as_get(val)
1753
1957
  return unless val
1754
1958
 
@@ -1781,6 +1985,15 @@ module Squared
1781
1985
  val.absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?(File.join('..', ''))
1782
1986
  end
1783
1987
 
1988
+ def checkdir?(val)
1989
+ if val.directory? && !val.empty?
1990
+ true
1991
+ else
1992
+ log&.warn "directory \"#{val}\" (#{val.empty? ? 'empty' : 'not found'})"
1993
+ false
1994
+ end
1995
+ end
1996
+
1784
1997
  def semmajor?(cur, want)
1785
1998
  (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
1786
1999
  end
@@ -1802,8 +2015,8 @@ module Squared
1802
2015
  val.is_a?(Array) && val.all? { |p| p.is_a?(Proc) || p.is_a?(Method) }
1803
2016
  end
1804
2017
 
1805
- def session_arg?(*args, target: @session, **kwargs)
1806
- !!target && OptionPartition.arg?(target, *args, **kwargs)
2018
+ def from_base?(val)
2019
+ task_invoked?(val, "#{val}:sync", 'default')
1807
2020
  end
1808
2021
 
1809
2022
  def from_sync?(*val)
@@ -1819,13 +2032,10 @@ module Squared
1819
2032
  return val if group && !(val = from_sync?(ac, group)).nil?
1820
2033
  return val if (base = workspace.find_base(self)) && !(val = from_sync?(ac, base.ref)).nil?
1821
2034
  return false if workspace.series.chain?(val = task_join(name, action))
2035
+ return true if task_invoked?(val) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
1822
2036
 
1823
- if task_invoked?(val) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
1824
- true
1825
- else
1826
- val = workspace.series.name_get(action)
1827
- val != action && invoked_sync?(val)
1828
- end
2037
+ val = workspace.series.name_get(action)
2038
+ val != action && invoked_sync?(val)
1829
2039
  end
1830
2040
 
1831
2041
  def success?(ret)
@@ -1836,6 +2046,10 @@ module Squared
1836
2046
  ARG[:BANNER] && !env('BANNER', equals: '0')
1837
2047
  end
1838
2048
 
2049
+ def pwd?
2050
+ path == Pathname.pwd
2051
+ end
2052
+
1839
2053
  def stdin?
1840
2054
  pipe == 0
1841
2055
  end
@@ -1848,22 +2062,19 @@ module Squared
1848
2062
  workspace.warning
1849
2063
  end
1850
2064
 
1851
- def has_value?(data, val)
1852
- return data.value?(val) if data.is_a?(Hash)
1853
- return data.is_a?(Enumerable) && data.to_a.include?(val) if !val.is_a?(Enumerable) || val.is_a?(Hash)
1854
-
1855
- val.to_a.any? do |obj|
1856
- case data
1857
- when Hash
1858
- data.value?(obj)
1859
- when Enumerable
1860
- data.to_a.include?(obj)
1861
- end
2065
+ def has_value?(data, other)
2066
+ case data
2067
+ when Hash
2068
+ other.is_a?(Enumerable) ? other.any? { |obj| data.value?(obj) } : data.value?(other)
2069
+ when Enumerable
2070
+ other.is_a?(Enumerable) ? other.any? { |obj| data.include?(obj) } : data.include?(other)
2071
+ else
2072
+ false
1862
2073
  end
1863
2074
  end
1864
2075
 
1865
2076
  def variables
1866
- Base.tasks + VAR_SET
2077
+ VAR_SET
1867
2078
  end
1868
2079
 
1869
2080
  def blocks