squared 0.2.13 → 0.3.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.
@@ -25,7 +25,7 @@ module Squared
25
25
  def bannerargs(*); end
26
26
 
27
27
  def tasks
28
- %i[build depend graph doc test copy clean].freeze
28
+ %i[build depend graph doc lint test copy clean].freeze
29
29
  end
30
30
 
31
31
  def as_path(val)
@@ -46,7 +46,7 @@ module Squared
46
46
  end
47
47
 
48
48
  def to_s
49
- super.match(/[^:]+\z/)[0]
49
+ super[/[^:]+\z/, 0]
50
50
  end
51
51
  end
52
52
 
@@ -68,12 +68,19 @@ module Squared
68
68
  @group = group&.to_s.freeze
69
69
  @depend = kwargs[:depend]
70
70
  @doc = kwargs[:doc]
71
+ @lint = kwargs[:lint]
71
72
  @test = kwargs[:test]
72
73
  @copy = kwargs[:copy]
73
74
  @clean = kwargs[:clean]
74
75
  @version = kwargs[:version]
75
- @exception = kwargs.key?(:exception) ? env_bool(kwargs[:exception]) : workspace.exception
76
- @pipe = kwargs.key?(:pipe) ? env_pipe(kwargs[:pipe]) : workspace.pipe
76
+ @envname = @name.gsub(/[^\w]+/, '_').upcase.freeze
77
+ kwargs[:exception] = 'PIPE_FAIL'
78
+ @exception = if kwargs.key?(:exception)
79
+ env_bool(kwargs[:exception], workspace.exception, strict: true)
80
+ else
81
+ workspace.exception
82
+ end
83
+ @pipe = kwargs.key?(:pipe) ? env_pipe(kwargs[:pipe], workspace.pipe, strict: true) : workspace.pipe
77
84
  @verbose = kwargs.key?(:verbose) ? kwargs[:verbose] : workspace.verbose
78
85
  @theme = if !@verbose
79
86
  {}
@@ -95,7 +102,6 @@ module Squared
95
102
  last: last,
96
103
  error: error
97
104
  }
98
- @envname = @name.gsub(/[^\w]+/, '_').upcase.freeze
99
105
  @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
100
106
  @parent = nil
101
107
  @global = false
@@ -116,6 +122,7 @@ module Squared
116
122
  end
117
123
  @depend = @script[:depend] if @depend.nil?
118
124
  @doc = @script[:doc] if @doc.nil?
125
+ @lint = @script[:lint] if @lint.nil?
119
126
  @test = @script[:test] if @test.nil?
120
127
  @clean = @script[:clean] if @clean.nil?
121
128
  @exclude = @script[:exclude] if @exclude.empty? && @script.key?(:exclude)
@@ -128,7 +135,7 @@ module Squared
128
135
  if @output[0].nil?
129
136
  if (scr = data[:script])
130
137
  @global = true
131
- script_set(scr, prod: kwargs[:prod])
138
+ script_set(scr, args: data.fetch(:args, kwargs[:args]), prod: kwargs[:prod])
132
139
  elsif (run = data[:run])
133
140
  @global = true
134
141
  run_set run
@@ -136,11 +143,11 @@ module Squared
136
143
  unless data[:env]
137
144
  if (scr = kwargs[:script])
138
145
  @global = false
139
- script_set scr
146
+ script_set(scr, args: kwargs[:args])
140
147
  elsif @script && !data[:global]
141
148
  if (scr = @script[:script])
142
149
  @global = false
143
- script_set scr
150
+ script_set(scr, args: @script.fetch(:args, kwargs[:args]))
144
151
  elsif (run = @script[:run])
145
152
  @global = false
146
153
  run_set run
@@ -151,6 +158,7 @@ module Squared
151
158
  @global = true
152
159
  run_set data[:run]
153
160
  end
161
+ @global = true
154
162
  end
155
163
 
156
164
  def initialize_events(ref, **)
@@ -163,24 +171,19 @@ module Squared
163
171
  return if @log
164
172
 
165
173
  log = log.is_a?(Hash) ? log.dup : { file: log }
166
- if (file = env('LOG_FILE'))
167
- file = DateTime.now.strftime(file)
168
- elsif (val = env('LOG_AUTO'))
169
- file = "#{@name}-%s.log" % [case val
170
- when 'y', 'year'
171
- Date.today.year
172
- when 'm', 'month'
173
- Date.today.strftime('%Y-%m')
174
- when 'd', 'day', '1'
175
- Date.today
176
- else
177
- val.include?('%') ? DateTime.now.strftime(val) : DateTime.now.strftime
178
- end]
179
- elsif (val = log[:file])
180
- file = val.is_a?(String) ? DateTime.now.strftime(val) : "#{@name}-#{Date.today}.log"
181
- end
182
- if file
183
- file = (val = env('LOG_DIR')) ? @workspace.home.join(val, file) : @workspace.home.join(file)
174
+ unless (file = env('LOG_FILE'))
175
+ file = case env('LOG_AUTO')
176
+ when 'y', 'year'
177
+ "#{@name}-#{Date.today.year}.log"
178
+ when 'm', 'month'
179
+ "#{@name}-#{Date.today.strftime('%Y-%m')}.log"
180
+ when 'd', 'day', '1'
181
+ "#{@name}-#{Date.today}.log"
182
+ end
183
+ end
184
+ if file ||= log[:file]
185
+ file = Date.today.strftime(file)
186
+ file = (dir = env('LOG_DIR')) ? @workspace.home.join(dir, file) : @workspace.home.join(file)
184
187
  begin
185
188
  file = file.realdirpath
186
189
  rescue StandardError => e
@@ -199,23 +202,23 @@ module Squared
199
202
  end
200
203
 
201
204
  def initialize_env(dev: nil, prod: nil, **)
202
- pre = "BUILD_#{@envname}"
203
- @dev = env_match("#{pre}_DEV", dev)
204
- @prod = env_match("#{pre}_PROD", prod)
205
+ @dev = env_match('BUILD', dev, suffix: 'DEV', strict: true)
206
+ @prod = env_match('BUILD', prod, suffix: 'PROD', strict: true)
205
207
  cmd = @output[0]
206
- unless cmd == false || cmd.is_a?(Array) || (val = env('BUILD', suffix: 'OPTS')).nil?
207
- @output[cmd ? 1 : 3] = shell_split(val, escape: false, join: true)
208
- end
209
- unless @output[2] == false || (val = env('BUILD', suffix: 'ENV')).nil?
208
+ if @output[2] != false && (val = env('BUILD', suffix: 'ENV'))
210
209
  begin
211
210
  data = JSON.parse(val)
212
211
  raise_error('invalid JSON object', val, hint: "#{prefix}_ENV") unless data.is_a?(Hash)
213
212
  rescue StandardError => e
214
- log&.warn e
213
+ log.warn e
215
214
  else
216
215
  @output[2] = data
217
216
  end
218
217
  end
218
+ if cmd != false && !cmd.is_a?(Array)
219
+ @output[cmd ? 1 : 3] = shell_split(val, join: true) if (val = env('BUILD', suffix: 'OPTS'))
220
+ @output[4] = shell_split(val, join: true) if cmd.nil? && (val = env('SCRIPT', suffix: 'OPTS'))
221
+ end
219
222
  @version = val if (val = env('BUILD', suffix: 'VERSION'))
220
223
  return unless (val = env('BUILD', strict: true))
221
224
 
@@ -247,23 +250,16 @@ module Squared
247
250
  when 'graph'
248
251
  next unless graph?
249
252
 
250
- check = lambda do |args|
251
- next args if (args = args.to_a).empty?
252
-
253
- param_guard(action, flag, args: args.reject { |val| name == val.to_s })
254
- end
255
-
256
- format_desc action, flag, 'project*'
257
- if flag == :run
258
- task flag do |_, args|
259
- graph check.(args)
260
- end
261
- else
262
- task flag do |_, args|
263
- out, done = graph(check.(args), out: [])
253
+ format_desc action, flag, '(-)project*'
254
+ task flag do |_, args|
255
+ args = param_guard(action, flag, args: args.to_a.reject { |val| name == val.to_s })
256
+ if flag == :run
257
+ graph args
258
+ else
259
+ out, done = graph(args, out: [])
264
260
  out.map! do |val|
265
261
  done.each_with_index do |proj, i|
266
- next unless Regexp.new(" #{Regexp.escape(proj.name)}(?:@\\d|\\z)") =~ val
262
+ next unless val =~ / #{Regexp.escape(proj.name)}(?:@\d|\z)/
267
263
 
268
264
  val += " (#{i.succ})"
269
265
  break
@@ -302,12 +298,12 @@ module Squared
302
298
  if val.directory? && !val.empty?
303
299
  true
304
300
  else
305
- log&.warn "workspace \"#{val}\" (#{val.empty? ? 'empty' : 'not found'})"
301
+ log.warn "workspace \"#{val}\" (#{val.empty? ? 'empty' : 'not found'})"
306
302
  false
307
303
  end
308
304
  end
309
- if path.is_a?(String) && (data = %r{^(.+)[\\/]\*+$}.match(path))
310
- return self unless checkdir.(path = basepath(data[1]))
305
+ if path.is_a?(String) && (seg = path[%r{^(.+)[\\/]\*+$}, 1])
306
+ return self unless checkdir.(path = basepath(seg))
311
307
 
312
308
  path = path.children.select { |val| checkdir.(val) }
313
309
  end
@@ -345,64 +341,87 @@ module Squared
345
341
 
346
342
  out = obj.link(self, *args, **kwargs, &blk) if obj.respond_to?(:link)
347
343
  if !out
348
- warn log_message(:warn, 'link not compatible', subject: obj.to_s, hint: name)
344
+ warn log_message(Logger::WARN, 'link not compatible', subject: obj.to_s, hint: name)
349
345
  elsif out.respond_to?(:build)
350
346
  out.build
351
347
  end
352
348
  self
353
349
  end
354
350
 
355
- def build(*args, from: :run, sync: invoked_sync?('build'), **)
351
+ def build(*args, sync: invoked_sync?('build'), from: :run, **)
356
352
  banner = verbose
357
353
  if args.empty?
358
354
  return unless from == :run
359
355
 
360
- cmd, opts, var, flags = @output
356
+ args = @output
361
357
  banner = verbose == 1 if task_invoked?('build', 'build:sync')
358
+ end
359
+ if args.all? { |val| val.is_a?(Array) }
360
+ cmd = []
361
+ var = {}
362
+ args.each do |val|
363
+ a, b, c, d, e = val
364
+ case b
365
+ when Hash
366
+ b = append_hash(b).join(' ')
367
+ when Enumerable
368
+ b = b.to_a.join(' ')
369
+ end
370
+ d = append_hash(d).join(' ') if d.is_a?(Hash)
371
+ if a
372
+ cmd << [a, d, b].compact.join(' ')
373
+ else
374
+ next unless respond_to?(:compose)
375
+
376
+ cmd << a if (a = compose(as_get(b), d, script: true, args: e, from: from))
377
+ end
378
+ var.merge!(c) if c.is_a?(Hash)
379
+ end
380
+ cmd = cmd.join(' && ')
362
381
  else
363
- cmd = args.shift
364
- opts = args.map { |val| shell_quote(val, force: false) }.join(' ')
382
+ cmd, opts, var, flags, scr = args
365
383
  end
366
384
  if cmd
385
+ cmd = as_get(cmd)
386
+ opts = compose(opts, script: false) if opts && respond_to?(:compose)
387
+ flags = append_hash(flags).join(' ') if flags.is_a?(Hash)
367
388
  case opts
368
- when String
369
- cmd = "#{cmd} #{opts}"
370
- when Array
371
- cmd = as_a(cmd).concat(opts).join(' && ')
389
+ when Hash
390
+ opts = append_hash(opts)
391
+ cmd = as_a(cmd).push(flags).concat(opts).compact.join(' ')
392
+ when Enumerable
393
+ cmd = as_a(cmd).concat(opts.to_a)
394
+ cmd.map! { |val| "#{val} #{flags}" } if flags
395
+ cmd = cmd.join(' && ')
372
396
  else
373
- cmd = [cmd, compose(opts, script: false)].compact.join(' ') unless opts == false || !respond_to?(:compose)
397
+ cmd = [cmd, flags, opts].compact.join(' ') if opts || flags
374
398
  end
375
399
  else
376
400
  return unless respond_to?(:compose)
377
401
 
378
- cmd = compose(opts, flags, from: from, script: true)
379
- from = :script if from == :run && script?
402
+ cmd = compose(as_get(opts), flags, script: true, args: scr, from: from)
380
403
  end
381
404
  run(cmd, var, from: from, banner: banner, sync: sync)
382
405
  end
383
406
 
384
407
  def depend(*, sync: invoked_sync?('depend'), **)
385
- return unless @depend
386
-
387
- run(@depend, from: :depend, sync: sync)
388
- end
389
-
390
- def copy(*, sync: invoked_sync?('copy'), **)
391
- return unless @copy
392
-
393
- run_s(@copy, from: :copy, sync: sync)
408
+ run_b(@depend, sync: sync, from: :depend)
394
409
  end
395
410
 
396
411
  def doc(*, sync: invoked_sync?('doc'), **)
397
- return unless @doc
412
+ run_b(@doc, sync: sync, from: :doc)
413
+ end
398
414
 
399
- build(*as_a(@doc), from: :doc, sync: sync)
415
+ def lint(*, sync: invoked_sync?('lint'), **)
416
+ run_b(@lint, sync: sync, from: :lint)
400
417
  end
401
418
 
402
419
  def test(*, sync: invoked_sync?('test'), **)
403
- return unless @test
420
+ run_b(@test, sync: sync, from: :test)
421
+ end
404
422
 
405
- build(*as_a(@test), from: :test, sync: sync)
423
+ def copy(*, sync: invoked_sync?('copy'), **)
424
+ run_b(@copy, sync: sync, from: :copy)
406
425
  end
407
426
 
408
427
  def clean(*, sync: invoked_sync?('clean'), **)
@@ -411,23 +430,31 @@ module Squared
411
430
  on :first, :clean
412
431
  case @clean
413
432
  when String
414
- run(@clean, from: :clean, sync: sync)
433
+ run_s(@clean, from: :clean, sync: sync)
434
+ when Hash
435
+ begin
436
+ @clean.each { |cmd, opts| build(cmd.to_s, opts, sync: sync) }
437
+ rescue StandardError => e
438
+ log.error e
439
+ ret = on(:error, from, e)
440
+ raise if @exception && ret != true
441
+ end
415
442
  when Enumerable
416
- (@clean.is_a?(Hash) ? @clean.values : as_a(@clean)).each do |val|
443
+ as_a(@clean).each do |val|
417
444
  val = val.to_s
418
445
  path = basepath(val)
419
446
  if path.directory? && val =~ %r{[\\/]\z}
420
- log&.warn "rm -rf #{path}"
421
- FileUtils.rm_rf(path, verbose: verbose)
447
+ log.warn "rm -rf #{path}"
448
+ path.rmtree(verbose: verbose)
422
449
  else
423
- log&.warn "rm #{path}"
450
+ log.warn "rm #{path}"
424
451
  (val.include?('*') ? Dir[path] : [path]).each do |file|
425
452
  next unless File.file?(file)
426
453
 
427
454
  begin
428
455
  File.delete(file)
429
456
  rescue StandardError => e
430
- log&.error e
457
+ log.error e
431
458
  end
432
459
  end
433
460
  end
@@ -447,8 +474,9 @@ module Squared
447
474
  end
448
475
  end
449
476
  end
450
- pass.concat(split_escape(val)) if (val = env('GRAPH_PASS', strict: true))
451
- data = graph_collect(self, start)
477
+ pass += split_escape(val) if (val = env('GRAPH', suffix: 'PASS'))
478
+ start, neg = start.partition { |name| !name.start_with?('-') }
479
+ data = graph_collect(self, start, pass: neg.map { |name| name[1..-1] })
452
480
  unless out
453
481
  data[name] << self
454
482
  on :first, :graph
@@ -482,6 +510,12 @@ module Squared
482
510
  (@events[name.to_sym] ||= {})[key.to_sym] = [block_given? ? [blk] + args : args, kwargs]
483
511
  end
484
512
 
513
+ def as(cmd, script, to = nil)
514
+ script = { "#{script}": to } if to
515
+ data = (@as ||= {})[cmd.to_sym] ||= {}
516
+ script.each { |key, val| data[key.to_s] = val }
517
+ end
518
+
485
519
  def variable_set(key, *val, **kwargs)
486
520
  if variables.include?(key)
487
521
  case key
@@ -506,7 +540,7 @@ module Squared
506
540
  instance_variable_set :"@#{key}", val.first
507
541
  end
508
542
  else
509
- log&.warn "variable_set: @#{key} (private)"
543
+ log.warn "variable_set: @#{key} (private)"
510
544
  end
511
545
  end
512
546
 
@@ -550,6 +584,10 @@ module Squared
550
584
  !!@doc
551
585
  end
552
586
 
587
+ def lint?
588
+ !!@lint
589
+ end
590
+
553
591
  def test?
554
592
  !!@test
555
593
  end
@@ -629,12 +667,19 @@ module Squared
629
667
  end
630
668
 
631
669
  def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, chdir: path, from: nil, **)
670
+ unless cmd
671
+ if warning?
672
+ from &&= from.to_s
673
+ warn log_message(Logger::WARN, from || 'unknown', subject: project, hint: 'no command given')
674
+ end
675
+ return
676
+ end
632
677
  cmd = session_done(cmd)
633
- log&.info cmd
634
- on :first, from if from
678
+ log.info cmd
679
+ on :first, from
635
680
  begin
636
681
  if cmd.match?(/\A[^:]+:[^:]/) && workspace.task_defined?(cmd)
637
- log&.warn "ENV was discarded: #{var}" if var
682
+ log.warn "ENV was discarded: #{var}" if var
638
683
  task_invoke(cmd, exception: exception, warning: warning?)
639
684
  else
640
685
  print_item format_banner(cmd, banner: banner) if sync
@@ -642,23 +687,33 @@ module Squared
642
687
  shell(*args, chdir: chdir, exception: exception)
643
688
  end
644
689
  rescue StandardError => e
645
- log&.error e
646
- ret = on(:error, from, e) if from
690
+ log.error e
691
+ ret = on(:error, from, e)
647
692
  raise unless ret == true
648
693
  else
649
- on :last, from if from
694
+ on :last, from
650
695
  end
651
696
  end
652
697
 
653
698
  def run_s(*cmd, env: nil, sync: true, banner: verbose != false, from: nil, **kwargs)
654
- on :first, from if from
699
+ on :first, from
655
700
  begin
656
701
  cmd.flatten.each { |val| run(val, env, sync: sync, banner: banner, **kwargs) }
657
702
  rescue StandardError => e
658
- ret = on(:error, from, e) if from
703
+ ret = on(:error, from, e)
659
704
  raise unless ret == true
660
705
  end
661
- on :last, from if from
706
+ on :last, from
707
+ end
708
+
709
+ def run_b(obj, from: nil, sync: true)
710
+ return unless obj
711
+
712
+ if obj.is_a?(Array) && obj.any? { |val| !val.is_a?(String) }
713
+ build(*obj, from: from, sync: sync)
714
+ else
715
+ run_s(obj.is_a?(Enumerable) ? obj.to_a : obj, from: from, sync: sync)
716
+ end
662
717
  end
663
718
 
664
719
  def graph_branch(target, data, tasks = nil, out = nil, sync: true, pass: [], done: [], depth: 0, last: false,
@@ -703,7 +758,7 @@ module Squared
703
758
 
704
759
  t = dedupe.(proj.name)
705
760
  j = if out
706
- if i == items.size - 1 || check.(post = items[(i + 1)..-1]).empty?
761
+ if i == items.size - 1 || check.(post = items[i + 1..-1]).empty?
707
762
  true
708
763
  elsif !t.empty? && depth > 0
709
764
  post.reject { |pr| t.include?(pr) }.empty?
@@ -745,9 +800,11 @@ module Squared
745
800
  done
746
801
  end
747
802
 
748
- def graph_collect(target, start = [], data: {}, root: [])
803
+ def graph_collect(target, start = [], data: {}, pass: [])
749
804
  deps = []
750
805
  (start.empty? ? target.instance_variable_get(:@graph) : start)&.each do |val|
806
+ next if pass.include?(val)
807
+
751
808
  if (obj = workspace.find(name: val))
752
809
  next unless obj.enabled?
753
810
 
@@ -755,20 +812,18 @@ module Squared
755
812
  else
756
813
  items = workspace.find(group: val, ref: val.to_sym)
757
814
  end
815
+
758
816
  items.each do |proj|
759
- name = proj.name
760
- if proj.graph? && !data.key?(name) && !root.include?(name)
761
- graph_collect(proj, data: data, root: root + [name, target.name])
762
- end
763
- next if (objs = data.fetch(name, [])).include?(target)
817
+ next if pass.include?(proj.name)
818
+
819
+ graph_collect(proj, data: data, pass: pass) if proj.graph? && !data.key?(proj.name)
820
+ next if (objs = data.fetch(proj.name, [])).include?(target)
764
821
 
765
822
  deps << proj
766
- deps.concat(objs)
823
+ deps += objs
767
824
  end
768
825
  end
769
- deps.uniq!
770
- deps.delete(target)
771
- data[target.name] = deps
826
+ data[target.name] = deps.uniq.reject { |proj| proj == target }
772
827
  data
773
828
  end
774
829
 
@@ -788,15 +843,29 @@ module Squared
788
843
  end
789
844
 
790
845
  def session(*cmd, prefix: cmd.first, main: true, options: true)
791
- prefix = stripext(prefix.to_s).upcase
846
+ prefix = prefix.to_s.upcase
792
847
  if (val = PATH[prefix] || PATH[prefix.to_sym])
793
848
  cmd[0] = shell_quote(val, force: false)
794
849
  end
795
- split_escape(val).each { |opt| cmd << fill_option(opt) } if options && (val = env("#{prefix}_OPTIONS"))
796
850
  ret = JoinSet.new(cmd.flatten(1))
851
+ if options && (val = env("#{prefix}_OPTIONS"))
852
+ split_escape(val).each { |opt| ret.last(fill_option(opt), /^(--?[^ =]+)[ =].+$/) }
853
+ end
797
854
  main ? @session = ret : ret
798
855
  end
799
856
 
857
+ def session_delete(*list, target: @session)
858
+ ret = []
859
+ list.each do |val|
860
+ pat = /^#{Regexp.escape(shell_option(val))}(?: |=|$)/
861
+ if (key = target.find { |opt| opt =~ pat })
862
+ target.delete(key)
863
+ ret << key
864
+ end
865
+ end
866
+ ret
867
+ end
868
+
800
869
  def session_output(*cmd, **kwargs)
801
870
  session(*cmd, main: false, options: false, **kwargs)
802
871
  end
@@ -809,54 +878,100 @@ module Squared
809
878
  cmd.done
810
879
  end
811
880
 
812
- def option(*args, target: @session, prefix: target&.first, **kwargs)
881
+ def option(*args, prefix: @session&.first, **kwargs)
813
882
  if prefix
814
883
  args.each do |val|
815
- ret = env("#{stripext(prefix)}_#{val.gsub(/\W/, '_')}".upcase, **kwargs)
884
+ ret = env("#{prefix}_#{val.gsub(/\W/, '_')}".upcase, **kwargs)
816
885
  return ret if ret
817
886
  end
818
887
  end
819
888
  nil
820
889
  end
821
890
 
822
- def option_partition(opts, list, target: @session, no: [], first: false, pass: ['='])
823
- out = []
891
+ def option_sanitize(opts, list, target: @session, no: nil, first: false, pass: ['='])
892
+ ret = []
893
+ reg = []
824
894
  bare = []
825
- reg, list = list.partition do |val|
826
- next unless (n = val.index('='))
827
-
828
- bare << val[0..(n - 1)] if val.end_with?('?')
829
- true
895
+ e = []
896
+ b = []
897
+ p = []
898
+ q = []
899
+ i = []
900
+ list = list.map do |val|
901
+ x, y = val.split('|')
902
+ if y
903
+ if (n = val.index('='))
904
+ x += val[n..-1]
905
+ end
906
+ [x, y]
907
+ else
908
+ x
909
+ end
910
+ end
911
+ list.flatten.each do |val|
912
+ if (n = val.index('='))
913
+ flag = val[0, n]
914
+ case val[n + 1]
915
+ when 'e'
916
+ e << flag
917
+ when 'b'
918
+ b << flag
919
+ when 'q'
920
+ q << flag
921
+ when 'p'
922
+ p << flag
923
+ when 'i'
924
+ i << flag
925
+ else
926
+ reg << Regexp.escape(flag)
927
+ end
928
+ bare << flag if val.end_with?('?')
929
+ else
930
+ bare << val
931
+ end
830
932
  end
831
- list += bare
832
- no = no.map { |val| (n = val.index('=')) ? val[0..(n - 1)] : val }
933
+ no = (no || []).map { |val| (n = val.index('=')) ? val[0, n] : val }
934
+ bare += no
833
935
  found = false
834
936
  opts.each do |opt|
835
- next out << opt if found
937
+ next ret << opt if found
836
938
 
837
- if list.include?(opt)
939
+ if bare.include?(opt)
838
940
  target << (opt.size == 1 ? "-#{opt}" : "--#{opt}")
839
941
  elsif opt.start_with?('no-') && no.include?(name = opt[3..-1])
840
942
  target << "--no-#{name}"
841
943
  else
842
- out << opt
944
+ if opt =~ /^([^=]+)=(.+)$/
945
+ a = $1
946
+ if e.include?(a)
947
+ target << shell_option(a, $2)
948
+ elsif q.include?(a)
949
+ target << quote_option(a, $2)
950
+ elsif p.include?(a)
951
+ target << quote_option(a, basepath($2))
952
+ elsif b.include?(a) || (i.include?(a) && /^\d+$/.match?($2))
953
+ target << basic_option(a, $2)
954
+ else
955
+ ret << opt
956
+ end
957
+ opt = a
958
+ else
959
+ ret << opt
960
+ end
843
961
  found = true if first && pass.none? { |val| opt.include?(val) }
844
962
  end
845
963
  end
846
- pat = Regexp.new("^(#{reg.map { |val| val[0..(val.index('=') - 1)] }.join('|')})=(.+)$")
847
- [out, pat]
964
+ [ret, reg.empty? ? /\A\s+\z/ : /^(#{reg.join('|')})=(.+)$/]
848
965
  end
849
966
 
850
- def option_clear(params, target: @session, **kwargs)
851
- return if params.empty?
852
-
967
+ def option_clear(opts, target: @session, **kwargs)
853
968
  kwargs[:subject] ||= target&.first
854
- kwargs[:hint] ||= 'unrecognized'
855
- warn log_message(:warn, params.join(', '), **kwargs)
969
+ kwargs[:hint] ||= 'not used'
970
+ warn log_message(Logger::WARN, opts.join(', '), **kwargs) unless opts.empty?
856
971
  end
857
972
 
858
973
  def print_item(*val)
859
- puts if @@print_order > 0
974
+ puts if @@print_order > 0 && verbose && !stdin?
860
975
  @@print_order += 1
861
976
  puts val unless val.empty? || (val.size == 1 && val.first.nil?)
862
977
  end
@@ -995,18 +1110,23 @@ module Squared
995
1110
  opts.each { |val| target << shell_option(flag, val) }
996
1111
  end
997
1112
 
998
- def append_value(*opts, target: @session, delim: false, escape: true, quote: false)
999
- return if (opts = opts.flatten).empty?
1000
-
1001
- target << '--' if delim
1002
- opts.each { |val| target << (escape ? shell_escape(val, quote: quote) : shell_quote(val)) }
1003
- end
1004
-
1005
- def append_hash(opts, target: @session)
1006
- out = target.to_s
1007
- opts.each do |key, val|
1008
- next if out =~ / #{Regexp.escape(shell_option(key))}(?: |=|$)/
1113
+ def append_hash(data, target: @session)
1114
+ target ||= []
1115
+ if (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
1116
+ type = "__#{type}__"
1117
+ if (extra = data[type] || data[type.to_sym]).is_a?(Hash)
1118
+ data = data.merge(extra)
1119
+ else
1120
+ extra = nil
1121
+ end
1122
+ end
1123
+ data.each do |key, val|
1124
+ next if (key = key.to_s).start_with?('__')
1009
1125
 
1126
+ if val.nil? || extra || session_arg?(key, target: target)
1127
+ session_delete(key, target: target)
1128
+ next if val.nil?
1129
+ end
1010
1130
  case val
1011
1131
  when Array
1012
1132
  append_repeat(key, val, target: target)
@@ -1018,14 +1138,50 @@ module Squared
1018
1138
  target << shell_option(key, val.is_a?(String) ? val : nil)
1019
1139
  end
1020
1140
  end
1141
+ target
1021
1142
  end
1022
1143
 
1023
- def append_first(list, target: @session, flag: true, equals: false, quote: false, escape: true, **kwargs)
1024
- list.each do |opt|
1025
- next unless (val = option(opt, target: target, **kwargs))
1144
+ def append_any(val, target: @session, delim: false)
1145
+ if delim && !target.include?('--')
1146
+ target << '--'
1147
+ else
1148
+ delim = false
1149
+ end
1150
+ case val
1151
+ when String
1152
+ target << val
1153
+ when Hash
1154
+ append_hash(val, target: target)
1155
+ when Enumerable
1156
+ if target.is_a?(Array)
1157
+ target.concat(val.to_a)
1158
+ else
1159
+ target.merge(val.to_a)
1160
+ end
1161
+ else
1162
+ target.delete('--') if delim
1163
+ end
1164
+ end
1165
+
1166
+ def append_value(*list, target: @session, delim: false, escape: false, quote: true)
1167
+ return if (list = list.flatten).empty?
1168
+
1169
+ target << '--' if delim && !target.include?('--')
1170
+ list.map do |val|
1171
+ target << (val = escape ? shell_escape(val, quote: quote) : shell_quote(val))
1172
+ val
1173
+ end
1174
+ end
1175
+
1176
+ def append_first(*list, target: @session, flag: true, equals: false, escape: true, quote: true, force: true,
1177
+ **kwargs)
1178
+ return if (list = list.flatten).empty?
1179
+
1180
+ list.flatten.each do |opt|
1181
+ next unless (val = option(opt, **kwargs))
1026
1182
 
1027
1183
  return target << (if flag
1028
- shell_option(opt, equals ? val : nil, quote: quote, escape: escape)
1184
+ shell_option(opt, equals ? val : nil, quote: quote, escape: escape, force: force)
1029
1185
  else
1030
1186
  shell_quote(val)
1031
1187
  end)
@@ -1033,21 +1189,36 @@ module Squared
1033
1189
  nil
1034
1190
  end
1035
1191
 
1036
- def append_option(list, target: @session, equals: false, quote: false, escape: true, **kwargs)
1037
- list.each do |flag|
1038
- next unless (val = option(flag, target: target, **kwargs))
1192
+ def append_option(*list, target: @session, no: false, equals: false, escape: true, quote: true, force: true,
1193
+ **kwargs)
1194
+ return if (list = list.flatten).empty?
1195
+
1196
+ ret = []
1197
+ list.flatten.each do |flag|
1198
+ next unless (val = option(flag, **kwargs))
1039
1199
 
1040
- target << shell_option(flag, equals ? val : nil, quote: quote, escape: escape)
1200
+ if val == '0' && no
1201
+ flag = "no-#{flag}"
1202
+ val = nil
1203
+ end
1204
+ ret << shell_option(flag, equals ? val : nil, escape: escape, quote: quote, force: force)
1041
1205
  end
1206
+ ret.each { |val| target << val } unless ret.empty?
1042
1207
  end
1043
1208
 
1044
1209
  def append_nocolor(target: @session)
1045
- target << '--no-color' if !ARG[:COLOR] || stdin? || option('no-color', target: target, ignore: false)
1210
+ target << '--no-color' if !ARG[:COLOR] || stdin? || option('no-color', ignore: false)
1211
+ end
1212
+
1213
+ def collect_hash(data, pass: [])
1214
+ ret = []
1215
+ data.each { |key, val| ret += val unless pass.include?(key) }
1216
+ ret
1046
1217
  end
1047
1218
 
1048
1219
  def param_guard(action, flag, args: nil, key: nil, pat: nil)
1049
1220
  if args && key
1050
- val = args.fetch(key, nil)
1221
+ val = args[key]
1051
1222
  return val unless val.nil? || (pat && !val.match?(pat))
1052
1223
 
1053
1224
  @session = nil
@@ -1082,17 +1253,15 @@ module Squared
1082
1253
  end
1083
1254
 
1084
1255
  def indexitem(val)
1085
- [$1.to_i, $2 && $2[1..-1]] if val =~ /\A#{Regexp.escape(indexchar)}(\d+)(:.+)?\z/
1256
+ return unless val =~ /\A\^(\d+)(:.+)?\z/
1257
+
1258
+ [$1.to_i, $2 && $2[1..-1]]
1086
1259
  end
1087
1260
 
1088
1261
  def indexerror(val, list = nil)
1089
1262
  raise_error("requested index #{val}", hint: list && "of #{list.size}")
1090
1263
  end
1091
1264
 
1092
- def indexchar
1093
- workspace.windows? ? '+' : '^'
1094
- end
1095
-
1096
1265
  def color(val)
1097
1266
  ret = theme[val]
1098
1267
  ret && !ret.empty? ? ret : [val]
@@ -1102,8 +1271,8 @@ module Squared
1102
1271
  val.compact.map { |s| color(s) }.flatten
1103
1272
  end
1104
1273
 
1105
- def on(event, action, *args, **kwargs)
1106
- return unless (obj = @events[event][action])
1274
+ def on(event, from, *args, **kwargs)
1275
+ return unless from && (obj = @events[event][from])
1107
1276
 
1108
1277
  if obj.is_a?(Array) && obj[1].is_a?(Hash)
1109
1278
  opts = kwargs.empty? ? obj[1] : obj[1].merge(kwargs)
@@ -1122,12 +1291,11 @@ module Squared
1122
1291
  end
1123
1292
  end
1124
1293
 
1125
- def pwd_set(done = nil, pass: false, from: nil, &blk)
1294
+ def pwd_set(done = nil, pass: false, from: nil, dryrun: false, &blk)
1126
1295
  pwd = Pathname.pwd
1127
1296
  if block_given?
1128
1297
  begin
1129
- pass = semscan(pass).join <= RUBY_VERSION if pass.is_a?(String)
1130
- if (path == pwd || pass == true) && !workspace.jruby_win?
1298
+ if path == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join >= RUBY_VERSION)
1131
1299
  ret = instance_eval(&blk)
1132
1300
  else
1133
1301
  Dir.chdir(path)
@@ -1135,9 +1303,11 @@ module Squared
1135
1303
  Dir.chdir(pwd)
1136
1304
  end
1137
1305
  rescue StandardError => e
1138
- log&.error e
1139
- ret = on(:error, from, e) if from
1140
- raise if exception && ret != true
1306
+ log.error e
1307
+ unless dryrun
1308
+ ret = on(:error, from, e)
1309
+ raise if exception && ret != true
1310
+ end
1141
1311
  else
1142
1312
  ret
1143
1313
  end
@@ -1157,6 +1327,8 @@ module Squared
1157
1327
  end
1158
1328
 
1159
1329
  def run_set(cmd, val = nil, opts: nil, **)
1330
+ return @output = cmd.dup if cmd.is_a?(Array)
1331
+
1160
1332
  unless @output[1] == false && !@output[0].nil?
1161
1333
  if opts == false
1162
1334
  @output[1] = false
@@ -1174,7 +1346,7 @@ module Squared
1174
1346
  @output[0] = cmd
1175
1347
  end
1176
1348
 
1177
- def script_set(cmd, prod: nil, **)
1349
+ def script_set(cmd, prod: nil, args: nil, **)
1178
1350
  return if @output[1] == false && @output[0].nil?
1179
1351
 
1180
1352
  @output[0] = nil
@@ -1183,31 +1355,36 @@ module Squared
1183
1355
  else
1184
1356
  cmd
1185
1357
  end
1358
+ @output[4] = args unless @output[4] == false || args.nil?
1359
+ end
1360
+
1361
+ def as_get(val)
1362
+ @global && (ret = @as && @as[from] && @as[from][val]) ? ret : val
1186
1363
  end
1187
1364
 
1188
1365
  def task_build(keys)
1189
1366
  namespace name do
1367
+ ws = workspace
1190
1368
  keys.each do |key|
1191
- next unless workspace.task_include?(self, key)
1369
+ next unless ws.task_include?(self, key)
1192
1370
 
1193
- action = workspace.series.name_get(key)
1194
- unless @pass.include?(key.to_s) || workspace.task_defined?(name, action)
1195
- workspace.task_desc(@desc, action)
1371
+ action = ws.series.name_get(key)
1372
+ unless @pass.include?(key.to_s) || ws.task_defined?(name, action) || ws.task_exclude?(action, self)
1373
+ ws.task_desc(@desc, action)
1196
1374
  task action do
1197
1375
  __send__(key)
1198
1376
  end
1199
1377
  end
1200
1378
  next if (items = @children.select { |item| item.task_include?(key) }).empty?
1201
1379
 
1202
- workspace.task_desc(@desc, action, 'workspace')
1380
+ ws.task_desc(@desc, action, 'workspace')
1203
1381
  task task_join(action, 'workspace') => items.map { |item| task_join(item.name, action) }
1204
1382
  end
1205
1383
  end
1206
1384
  end
1207
1385
 
1208
1386
  def projectpath?(val)
1209
- val = Pathname.new(val).cleanpath
1210
- val.absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?(File.join('..', ''))
1387
+ Pathname.new(val).absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?('..')
1211
1388
  end
1212
1389
 
1213
1390
  def semmajor?(cur, want)
@@ -1225,6 +1402,13 @@ module Squared
1225
1402
  end
1226
1403
  end
1227
1404
 
1405
+ def session_arg?(*list, target: @session, value: false)
1406
+ list.any? do |val|
1407
+ pat = /^#{Regexp.escape(shell_option(val))}#{value ? '[ =].' : '(?:[ =]|$)'}/
1408
+ target.any? { |opt| opt =~ pat }
1409
+ end
1410
+ end
1411
+
1228
1412
  def from_sync?(*val)
1229
1413
  if task_invoked?(key = task_join(*val))
1230
1414
  !workspace.task_defined?(key, 'sync')