squared 0.1.4 → 0.2.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.
@@ -14,7 +14,7 @@ module Squared
14
14
  include Utils
15
15
  include Rake::DSL
16
16
 
17
- VAR_SET = %i[parent global envname dependfile theme run script env].freeze
17
+ VAR_SET = %i[parent global envname dependfile theme run script env pass].freeze
18
18
  SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?\b/.freeze
19
19
  private_constant :VAR_SET, :SEM_VER
20
20
 
@@ -50,18 +50,17 @@ module Squared
50
50
  end
51
51
  end
52
52
 
53
- @@tasks = {
54
- "#{ref}": {
55
- graph: %i[run print].freeze
56
- }.freeze
57
- }
53
+ (@@tasks = {})[ref] = {
54
+ 'graph' => %i[run print].freeze
55
+ }.freeze
56
+ @@task_desc = Rake::TaskManager.record_task_metadata
58
57
  @@print_order = 0
59
58
 
60
- attr_reader :name, :project, :workspace, :path, :exception, :pipe, :theme, :group, :parent,
61
- :dependfile, :verbose
59
+ attr_reader :name, :project, :workspace, :path, :theme, :exception, :pipe, :verbose,
60
+ :group, :parent, :dependfile
62
61
 
63
62
  def initialize(workspace, path, name, *, group: nil, graph: nil, pass: nil, exclude: nil,
64
- common: ARG[:COMMON], **kwargs)
63
+ first: {}, last: {}, error: {}, common: ARG[:COMMON], **kwargs)
65
64
  @path = path
66
65
  @workspace = workspace
67
66
  @name = name.to_s.freeze
@@ -72,6 +71,7 @@ module Squared
72
71
  @test = kwargs[:test]
73
72
  @copy = kwargs[:copy]
74
73
  @clean = kwargs[:clean]
74
+ @version = kwargs[:version]
75
75
  @exception = kwargs.key?(:exception) ? env_bool(kwargs[:exception]) : workspace.exception
76
76
  @pipe = kwargs.key?(:pipe) ? env_pipe(kwargs[:pipe]) : workspace.pipe
77
77
  @verbose = kwargs.key?(:verbose) ? kwargs[:verbose] : workspace.verbose
@@ -88,8 +88,13 @@ module Squared
88
88
  @graph = if graph
89
89
  as_a(graph, workspace.prefix ? ->(val) { workspace.task_name(val).to_sym } : :to_sym).freeze
90
90
  end
91
- @pass = (pass ? as_a(pass, :to_sym) : []).freeze
91
+ @pass = (pass ? as_a(pass) : []).freeze
92
92
  @exclude = (exclude ? as_a(exclude, :to_sym) : []).freeze
93
+ @events = {
94
+ first: first,
95
+ last: last,
96
+ error: error
97
+ }
93
98
  @envname = @name.gsub(/[^\w]+/, '_').upcase.freeze
94
99
  @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
95
100
  @parent = nil
@@ -115,6 +120,7 @@ module Squared
115
120
  @clean = @script[:clean] if @clean.nil?
116
121
  @exclude = @script[:exclude] if @exclude.empty? && @script.key?(:exclude)
117
122
  end
123
+ initialize_events(ref, **kwargs)
118
124
  initialize_logger(**kwargs)
119
125
  return if @output[0] == false
120
126
 
@@ -147,6 +153,12 @@ module Squared
147
153
  end
148
154
  end
149
155
 
156
+ def initialize_events(ref, **)
157
+ return unless (events = @workspace.events_get(group: @group, ref: ref))
158
+
159
+ events.each { |task, data| data.each { |ev, blk| (@events[ev] ||= {})[task] ||= blk } }
160
+ end
161
+
150
162
  def initialize_logger(log: nil, **)
151
163
  return if @log
152
164
 
@@ -193,11 +205,13 @@ module Squared
193
205
  begin
194
206
  data = JSON.parse(val)
195
207
  raise_error('invalid JSON object', val, hint: "#{prefix}_ENV") unless data.is_a?(Hash)
196
- @output[2] = data
197
208
  rescue StandardError => e
198
209
  log.warn e
210
+ else
211
+ @output[2] = data
199
212
  end
200
213
  end
214
+ @version = val if (val = env('BUILD', suffix: 'VERSION'))
201
215
  return unless (val = env('BUILD', strict: true))
202
216
 
203
217
  @global = false
@@ -214,49 +228,37 @@ module Squared
214
228
  Base.ref
215
229
  end
216
230
 
217
- def populate(*)
218
- namespace name do
219
- workspace.series.each_key do |key|
220
- next unless workspace.task_include?(self, key)
221
-
222
- s = workspace.series.name_get(key)
223
- unless workspace.task_defined?(name, s)
224
- desc message(@desc, s)
225
- task s do
226
- __send__(key)
227
- end
228
- end
229
- next if (items = @children.select { |item| workspace.task_include?(item, key) }).empty?
230
-
231
- desc message(@desc, s, 'workspace')
232
- task task_join(s, 'workspace') => items.map { |item| task_join(item.name, s) }
233
- end
234
- next unless ref?(Base.ref)
231
+ def populate(keys, **)
232
+ task_build keys
233
+ return unless ref?(Base.ref)
235
234
 
235
+ namespace name do
236
236
  @@tasks[Base.ref].each do |action, flags|
237
+ next if @pass.include?(action)
238
+
237
239
  namespace action do
238
240
  flags.each do |flag|
239
241
  case action
240
- when :graph
242
+ when 'graph'
241
243
  next unless graph?
242
244
 
243
245
  check = lambda do |args|
244
246
  next args if (args = args.to_a).empty?
245
247
 
246
- guard_params(action, flag, args: args.reject { |val| name == val.to_s })
248
+ param_guard(action, flag, args: args.reject { |val| name == val.to_s })
247
249
  end
248
250
 
249
- desc format_desc(action, flag, 'project*')
251
+ format_desc action, flag, 'project*'
250
252
  if flag == :run
251
- task flag, [:project] do |_, args|
253
+ task flag do |_, args|
252
254
  graph check.(args)
253
255
  end
254
256
  else
255
- task flag, [:project] do |_, args|
257
+ task flag do |_, args|
256
258
  out, done = graph(check.(args), out: [])
257
259
  out.map! do |val|
258
260
  done.each_with_index do |proj, i|
259
- next unless val.end_with?(" #{proj.name}")
261
+ next unless Regexp.new(" #{Regexp.escape(proj.name)}(?:@\\d|\\z)") =~ val
260
262
 
261
263
  val += " (#{i.succ})"
262
264
  break
@@ -277,6 +279,10 @@ module Squared
277
279
  end
278
280
  end
279
281
 
282
+ def generate(keys, **)
283
+ task_build keys
284
+ end
285
+
280
286
  def with(**kwargs, &blk)
281
287
  @withargs = kwargs.empty? ? nil : kwargs
282
288
  if block_given?
@@ -329,7 +335,19 @@ module Squared
329
335
  self
330
336
  end
331
337
 
332
- def build(*args, sync: invoked_sync?('build'), **)
338
+ def inject(obj, *args, **kwargs, &blk)
339
+ return self unless enabled?
340
+
341
+ out = obj.link(self, *args, **kwargs, &blk) if obj.respond_to?(:link)
342
+ if !out
343
+ warn log_message(:warn, 'link not compatible', subject: obj.to_s, hint: name)
344
+ elsif out.respond_to?(:build)
345
+ out.build
346
+ end
347
+ self
348
+ end
349
+
350
+ def build(*args, from: :build, sync: invoked_sync?('build'), **)
333
351
  if args.empty?
334
352
  cmd, opts, var, flags = @output
335
353
  banner = verbose == 1
@@ -343,52 +361,63 @@ module Squared
343
361
  when String
344
362
  cmd = "#{cmd} #{opts}"
345
363
  when Array
346
- cmd = as_a(cmd).concat(opts).join(' && ')
364
+ cmd = cmd.join(' && ')
347
365
  else
348
366
  cmd = [cmd, compose(opts, script: false)].compact.join(' ') unless opts == false || !respond_to?(:compose)
349
367
  end
350
368
  else
351
369
  return unless respond_to?(:compose)
352
370
 
353
- cmd = compose(opts, flags, script: true)
371
+ cmd = compose(opts, flags, from: from, script: true)
354
372
  end
355
- run(cmd, var, banner: banner, sync: sync)
373
+ run(cmd, var, from: from, banner: banner, sync: sync)
356
374
  end
357
375
 
358
376
  def depend(*, sync: invoked_sync?('depend'), **)
359
- run(@depend, sync: sync) if @depend
377
+ return unless @depend
378
+
379
+ run(@depend, from: :depend, sync: sync)
360
380
  end
361
381
 
362
382
  def copy(*, sync: invoked_sync?('copy'), **)
363
- run_s(@copy, sync: sync) if @copy
383
+ return unless @copy
384
+
385
+ run_s(@copy, from: :copy, sync: sync)
364
386
  end
365
387
 
366
388
  def doc(*, sync: invoked_sync?('doc'), **)
367
- build(*as_a(@doc), sync: sync) if @doc
389
+ return unless @doc
390
+
391
+ build(@doc, from: :doc, sync: sync)
368
392
  end
369
393
 
370
394
  def test(*, sync: invoked_sync?('test'), **)
371
- build(*as_a(@test), sync: sync) if @test
395
+ return unless @test
396
+
397
+ build(@test, from: :test, sync: sync)
372
398
  end
373
399
 
374
- def clean(*)
400
+ def clean(*, sync: invoked_sync?('clean'), **)
401
+ return unless @clean
402
+
403
+ on :first, :clean
375
404
  case @clean
376
405
  when String
377
- run_s(@clean, sync: invoked_sync?('clean'))
406
+ run_s(@clean, from: :clean, sync: sync)
378
407
  when Enumerable
379
- (@clean.is_a?(Hash) ? @clean.values : as_a(@clean)).each do |val|
380
- val = val.to_s
381
- path = basepath(val)
382
- if path.directory? && val =~ %r{[\\/]$}
383
- log.warn "rm -rf #{path}"
384
- path.rmtree
408
+ as_a(@clean).each do |val|
409
+ if val =~ %r{[\\/]$}
410
+ dir = Pathname.new(val.to_s)
411
+ dir = basepath(dir) unless dir.absolute?
412
+ next unless dir.directory?
413
+
414
+ log.warn "rm -rf #{dir}"
415
+ dir.rmtree
385
416
  else
386
- files = val.include?('*') ? Dir[path] : [path]
417
+ files = val.include?('*') ? Dir[basepath(val)] : [basepath(val)]
387
418
  files.each do |file|
388
- next unless File.file?(file)
389
-
390
419
  begin
391
- File.delete(file)
420
+ File.delete(file) if File.file?(file)
392
421
  rescue StandardError => e
393
422
  log.error e
394
423
  end
@@ -396,6 +425,7 @@ module Squared
396
425
  end
397
426
  end
398
427
  end
428
+ on :last, :clean
399
429
  end
400
430
 
401
431
  def graph(start = [], tasks = nil, sync: invoked_sync?('graph'), pass: [], out: nil)
@@ -411,9 +441,37 @@ module Squared
411
441
  end
412
442
  pass.concat(split_escape(val)) if (val = env('GRAPH_PASS', strict: true))
413
443
  data = graph_collect(self, start)
414
- data[name] << self unless out
415
- done = run_graph(data, name, tasks, out, sync: sync, pass: pass)
416
- [out, done] if out
444
+ unless out
445
+ data[name] << self
446
+ on :first, :graph
447
+ end
448
+ begin
449
+ done = graph_branch(self, data, tasks, out, sync: sync, pass: pass)
450
+ rescue StandardError => e
451
+ ret = on(:error, :graph, e)
452
+ raise unless ret == true
453
+ end
454
+ if out
455
+ [out, done]
456
+ else
457
+ on :last, :graph
458
+ end
459
+ end
460
+
461
+ def first(key, *args, **kwargs, &blk)
462
+ event(:first, key, *args, **kwargs, &blk)
463
+ end
464
+
465
+ def last(key, *args, **kwargs, &blk)
466
+ event(:last, key, *args, **kwargs, &blk)
467
+ end
468
+
469
+ def error(key, *args, **kwargs, &blk)
470
+ event(:error, key, *args, **kwargs, &blk)
471
+ end
472
+
473
+ def event(name, key, *args, **kwargs, &blk)
474
+ (@events[name.to_sym] ||= {})[key.to_sym] = [block_given? ? [blk] + args : args, kwargs]
417
475
  end
418
476
 
419
477
  def variable_set(key, *val, **kwargs)
@@ -444,7 +502,7 @@ module Squared
444
502
  end
445
503
  end
446
504
 
447
- def enabled?(ref = nil)
505
+ def enabled?(ref = nil, **)
448
506
  return false if ref && !ref?(ref)
449
507
 
450
508
  path.directory? && !path.empty?
@@ -500,7 +558,13 @@ module Squared
500
558
  @prod != false && workspace.prod?(pat: @prod, **scriptargs)
501
559
  end
502
560
 
503
- def version(*); end
561
+ def task_include?(key, ref = nil)
562
+ workspace.task_include?(self, key, ref) && !@pass.include?(key.to_s)
563
+ end
564
+
565
+ def version(*)
566
+ @version
567
+ end
504
568
 
505
569
  def dependtype(*)
506
570
  @dependindex ? @dependindex.succ : 0
@@ -528,6 +592,10 @@ module Squared
528
592
  ret
529
593
  end
530
594
 
595
+ def localname
596
+ workspace.task_localname(name)
597
+ end
598
+
531
599
  def inspect
532
600
  "#<#{self.class}: #{name} => #{self}>"
533
601
  end
@@ -546,11 +614,12 @@ module Squared
546
614
  puts_oe(*args, pipe: pipe)
547
615
  end
548
616
 
549
- def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, chdir: path, **)
617
+ def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, chdir: path, from: nil, **)
550
618
  cmd = session_done(cmd)
551
619
  log.info cmd
620
+ on :first, from if from
552
621
  begin
553
- if cmd =~ /\A[^:]+:[^:]/ && workspace.task_defined?(cmd)
622
+ if cmd.match?(/\A[^:]+:[^:]/) && workspace.task_defined?(cmd)
554
623
  log.warn "ENV was discarded: #{var}" if var
555
624
  task_invoke(cmd, exception: exception, warning: warning?)
556
625
  else
@@ -560,16 +629,27 @@ module Squared
560
629
  end
561
630
  rescue StandardError => e
562
631
  log.error e
563
- raise
632
+ ret = on(:error, from, e) if from
633
+ raise unless ret == true
634
+ else
635
+ on :last, from if from
564
636
  end
565
637
  end
566
638
 
567
- def run_s(*cmd, env: nil, banner: verbose != false, **kwargs)
568
- cmd.each { |val| run(val, env, banner: banner, **kwargs) }
639
+ def run_s(*cmd, env: nil, sync: true, banner: verbose != false, from: nil, **kwargs)
640
+ on :first, from if from
641
+ begin
642
+ cmd.each { |val| run(val, env, sync: sync, banner: banner, **kwargs) }
643
+ rescue StandardError => e
644
+ ret = on(:error, from, e) if from
645
+ raise unless ret == true
646
+ end
647
+ on :last, from if from
569
648
  end
570
649
 
571
- def run_graph(data, start, tasks = nil, out = nil, sync: true, pass: [], done: [], depth: 0, last: false,
572
- single: false)
650
+ def graph_branch(target, data, tasks = nil, out = nil, sync: true, pass: [], done: [], depth: 0, last: false,
651
+ single: false)
652
+ tag = ->(proj) { "#{proj.name}#{SEM_VER =~ proj.version ? "@#{proj.version}" : ''}" }
573
653
  check = ->(deps) { deps.reject { |val| done.include?(val) } }
574
654
  dedupe = lambda do |name|
575
655
  next [] unless (ret = data[name])
@@ -581,6 +661,7 @@ module Squared
581
661
  end
582
662
  ret
583
663
  end
664
+ start = target.name
584
665
  if depth == 0
585
666
  items = check.(dedupe.(start))
586
667
  single = items.size == 1
@@ -589,24 +670,24 @@ module Squared
589
670
  end
590
671
  if out
591
672
  a, b, c, d, e = ARG[:GRAPH]
673
+ f = tag.(target)
592
674
  out << case depth
593
675
  when 0
594
- start
676
+ f
595
677
  when 1
596
678
  if items.empty?
597
- "#{d}#{b * 4} #{start}"
679
+ "#{d}#{b * 4} #{f}"
598
680
  else
599
- "#{last ? d : c}#{b * 3}#{e} #{start}"
681
+ "#{last ? d : c}#{b * 3}#{e} #{f}"
600
682
  end
601
683
  else
602
- "#{single ? ' ' : a}#{' ' * (depth - 1)}#{last ? d : c}#{b * 3}#{items.empty? ? b : e} #{start}"
684
+ "#{single ? ' ' : a}#{' ' * (depth - 1)}#{last ? d : c}#{b * 3}#{items.empty? ? b : e} #{f}"
603
685
  end
604
686
  end
605
687
  items.each_with_index do |proj, i|
606
688
  next if done.include?(proj)
607
689
 
608
- s = proj.name
609
- t = dedupe.(s)
690
+ t = dedupe.(proj.name)
610
691
  j = if out
611
692
  if i == items.size - 1 || check.(post = items[i + 1..-1]).empty?
612
693
  true
@@ -614,35 +695,35 @@ module Squared
614
695
  post.reject { |pr| t.include?(pr) }.empty?
615
696
  end
616
697
  end
617
- unless start == s || (none = check.(t).empty?)
618
- run_graph(data, s, tasks, out, sync: sync, pass: pass, done: done, depth: depth.succ, single: single,
619
- last: j == true)
698
+ unless start == proj.name || (none = check.(t).empty?)
699
+ graph_branch(proj, data, tasks, out, sync: sync, pass: pass, done: done, depth: depth.succ,
700
+ single: single, last: j == true)
620
701
  end
621
702
  if !out
622
- unless tasks || !(script = workspace.script_get(:graph, group: proj.group, ref: proj.allref))
703
+ if !tasks && (script = workspace.script_get(:graph, group: proj.group, ref: proj.allref))
623
704
  group = script[:graph]
624
705
  end
625
706
  (tasks || group || (dev? ? ['build', 'copy'] : ['depend', 'build'])).each do |meth|
626
707
  next if pass.include?(meth)
627
708
 
628
- if workspace.task_defined?(cmd = task_join(s, meth))
709
+ if workspace.task_defined?(cmd = task_join(proj.name, meth))
629
710
  run(cmd, sync: false, banner: false)
630
- elsif proj.has?(meth)
711
+ elsif proj.has?(meth, tasks || group ? nil : workspace.baseref)
631
712
  proj.__send__(meth.to_sym, sync: sync)
632
713
  end
633
714
  end
634
715
  elsif none
635
716
  a, b, c, d = ARG[:GRAPH]
636
717
  out << if depth == 0
637
- "#{i == items.size - 1 ? d : c}#{b * 4} #{s}"
718
+ "#{i == items.size - 1 ? d : c}#{b * 4} #{tag.(proj)}"
638
719
  else
639
- f = ''.dup
720
+ s = ''.dup
640
721
  k = 0
641
722
  while k < depth
642
- f += "#{(last && !j) || (j && k > 0 && k == depth - 1) || single ? ' ' : a} "
723
+ s += "#{(last && !j) || (j && k > 0 && k == depth - 1) || single ? ' ' : a} "
643
724
  k += 1
644
725
  end
645
- f + "#{j ? d : c}#{b * 3} #{s}"
726
+ s + "#{j ? d : c}#{b * 3} #{tag.(proj)}"
646
727
  end
647
728
  end
648
729
  done << proj
@@ -653,11 +734,20 @@ module Squared
653
734
  def graph_collect(target, start = [], data: {})
654
735
  deps = []
655
736
  (start.empty? ? target.instance_variable_get(:@graph) : start)&.each do |val|
656
- next unless (proj = workspace.find(name: val)) && proj.enabled?
737
+ if (obj = workspace.find(name: val))
738
+ next unless obj.enabled?
657
739
 
658
- deps << proj
659
- graph_collect(proj, data: data) if proj.graph? && !data.key?(proj.name)
660
- deps += data.fetch(val, [])
740
+ items = [obj]
741
+ else
742
+ items = workspace.find(group: val, ref: val.to_sym)
743
+ end
744
+ items.each do |proj|
745
+ graph_collect(proj, data: data) if proj.graph? && !data.key?(proj.name)
746
+ next if (objs = data.fetch(proj.name, [])).include?(target)
747
+
748
+ deps << proj
749
+ deps += objs
750
+ end
661
751
  end
662
752
  data[target.name] = deps.uniq.reject { |proj| proj == target }
663
753
  data
@@ -678,15 +768,22 @@ module Squared
678
768
  ret.empty? || (ignore && as_a(ignore).any? { |val| ret == val.to_s }) ? default : ret
679
769
  end
680
770
 
681
- def session(*cmd, prefix: cmd.first)
682
- if (val = env("#{prefix.upcase}_OPTIONS"))
683
- split_escape(val).each { |opt| cmd << fill_option(opt) }
771
+ def session(*cmd, prefix: cmd.first, main: true, options: true)
772
+ prefix = prefix.to_s.upcase
773
+ if (val = PATH[prefix] || PATH[prefix.to_sym])
774
+ cmd[0] = shell_quote(val, force: false)
684
775
  end
685
- @session = JoinSet.new(cmd)
776
+ split_escape(val).each { |opt| cmd << fill_option(opt) } if options && (val = env("#{prefix}_OPTIONS"))
777
+ ret = JoinSet.new(cmd.flatten(1))
778
+ main ? @session = ret : ret
779
+ end
780
+
781
+ def session_output(*cmd, **kwargs)
782
+ session(*cmd, main: false, options: false, **kwargs)
686
783
  end
687
784
 
688
785
  def session_done(cmd)
689
- return cmd.to_s unless cmd.respond_to?(:done)
786
+ return cmd unless cmd.respond_to?(:done)
690
787
 
691
788
  raise_error('no args were added', hint: cmd.first || name) unless cmd.size > 1
692
789
  @session = nil if cmd == @session
@@ -703,6 +800,40 @@ module Squared
703
800
  nil
704
801
  end
705
802
 
803
+ def option_partition(opts, list, target: @session, no: [], first: false, pass: ['='])
804
+ out = []
805
+ bare = []
806
+ reg, list = list.partition do |val|
807
+ next unless (n = val.index('='))
808
+
809
+ bare << val[0..n - 1] if val.end_with?('?')
810
+ true
811
+ end
812
+ list += bare
813
+ no = no.map { |val| (n = val.index('=')) ? val[0..n - 1] : val }
814
+ found = false
815
+ opts.each do |opt|
816
+ next out << opt if found
817
+
818
+ if list.include?(opt)
819
+ target << (opt.size == 1 ? "-#{opt}" : "--#{opt}")
820
+ elsif opt.start_with?('no-') && no.include?(name = opt[3..-1])
821
+ target << "--no-#{name}"
822
+ else
823
+ out << opt
824
+ found = true if first && pass.none? { |val| opt.include?(val) }
825
+ end
826
+ end
827
+ pat = Regexp.new("^(#{reg.map { |val| val[0..val.index('=') - 1] }.join('|')})=(.+)$")
828
+ [out, pat]
829
+ end
830
+
831
+ def option_clear(params, target: @session, **kwargs)
832
+ kwargs[:subject] ||= target&.first
833
+ kwargs[:hint] ||= 'not used'
834
+ warn log_message(:warn, params.join(', '), **kwargs) unless params.empty?
835
+ end
836
+
706
837
  def print_item(*val)
707
838
  puts if @@print_order > 0 && verbose && !stdin?
708
839
  @@print_order += 1
@@ -715,7 +846,7 @@ module Squared
715
846
  if styles.any? { |s| s.to_s.end_with?('!') }
716
847
  pad = 1
717
848
  elsif !client && styles.size <= 1
718
- styles = %i[bold] + styles
849
+ styles = [:bold] + styles
719
850
  end
720
851
  end
721
852
  n = Project.max_width(lines)
@@ -748,32 +879,33 @@ module Squared
748
879
  ret.join("\n")
749
880
  end
750
881
 
751
- def format_desc(action, flag, opts = nil, req: nil, arg: 'opts*')
752
- return unless Rake::TaskManager.record_task_metadata
882
+ def format_desc(action, flag, opts = nil, **kwargs)
883
+ return unless @@task_desc
753
884
 
754
- opts = "#{arg}=#{opts.join(',')}" if opts.is_a?(Array)
755
- out = [@desc]
756
- if flag
757
- out << action
758
- else
759
- flag = action
760
- end
761
- req &&= opts ? "#{req}," : "[#{req}]"
762
- out << (opts ? "#{flag}[#{req}#{opts}]" : "#{flag}#{req}")
763
- message(*out)
885
+ ret = [@desc, action]
886
+ ret << flag if flag
887
+ workspace.format_desc(ret, opts, **kwargs)
764
888
  end
765
889
 
766
890
  def format_banner(cmd, banner: true)
767
891
  return unless banner && ARG[:BANNER]
768
892
 
769
893
  if (data = workspace.banner_get(*@ref, group: group))
894
+ return if data.empty?
895
+
770
896
  client = true
771
897
  else
772
- data = { command: true, order: %i[path], styles: theme[:banner], border: theme[:border] }
898
+ data = { command: true, order: [:path], styles: theme[:banner], border: theme[:border] }
773
899
  end
774
900
  if verbose
775
901
  out = []
776
- out << cmd.sub(/\A\S+/, &:upcase) if data[:command]
902
+ if data[:command]
903
+ if /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+)) /.match(cmd)
904
+ path = $3 || $2 || $1
905
+ cmd = cmd.sub(path, File.basename(path).upcase)
906
+ end
907
+ out << cmd
908
+ end
777
909
  data[:order].each do |val|
778
910
  if val.is_a?(Array)
779
911
  s = ' '
@@ -803,14 +935,15 @@ module Squared
803
935
  def format_list(items, cmd, type, grep: [], from: nil, each: nil)
804
936
  reg = grep.map { |val| Regexp.new(val) }
805
937
  out = []
806
- if (pad = items.size) > 0
807
- pad = pad.to_s.size
938
+ unless items.empty?
939
+ pad = items.size.to_s.size
808
940
  items.each_with_index do |val, i|
809
- next unless reg.empty? || reg.any? { |pat| pat.match?(val[0]) }
941
+ next unless reg.empty? || reg.any? { |pat| val[0] =~ pat }
810
942
 
811
943
  out << "#{(i + 1).to_s.rjust(pad)}. #{each ? each.(val) : val[0]}"
812
944
  end
813
945
  end
946
+ sub = [headerstyle]
814
947
  if out.empty?
815
948
  out = ["No #{type} were found:", '']
816
949
  unless grep.empty?
@@ -820,54 +953,81 @@ module Squared
820
953
  end
821
954
  if from
822
955
  out << from
823
- pat = /\A(#{Regexp.escape(out.last)})(.*)\z/m
956
+ pat = /\A(#{Regexp.escape(from)})(.*)\z/
824
957
  end
825
958
  else
826
- pat = /\A(\s*\d+\.)(.+)\z/m
959
+ pat = /\A(\s*\d+\.)(.+)\z/
960
+ unless grep.empty?
961
+ footer = "#{out.size} found"
962
+ sub << { pat: /\A(\d+)( .+)\z/, styles: theme[:inline] }
963
+ end
827
964
  end
828
- sub = [headerstyle]
829
965
  sub << { pat: pat, styles: theme[:active] } if pat
830
- emphasize(out, title: task_join(name, cmd), border: borderstyle, sub: sub)
966
+ emphasize(out, title: task_join(name, cmd), border: borderstyle, sub: sub, footer: footer, right: true)
831
967
  end
832
968
 
833
969
  def empty_status(msg, title, obj, always: false)
834
970
  "#{msg}#{!always && (!obj || obj == 0 || obj.to_s.empty?) ? '' : message(hint: message(title, obj.to_s))}"
835
971
  end
836
972
 
837
- def append_repeat(flag, opts)
838
- opts.each { |val| @session << shell_option(flag, val, quote: true) }
973
+ def append_repeat(flag, opts, target: @session)
974
+ opts.each { |val| target << shell_option(flag, val, quote: true) }
839
975
  end
840
976
 
841
- def append_value(opts, delim: false, escape: true)
842
- @session << '--' if delim && !opts.empty?
843
- opts.each { |val| @session << (escape ? shell_escape(val) : shell_quote(val)) }
977
+ def append_value(opts, target: @session, delim: false, escape: true)
978
+ return if opts.empty?
979
+
980
+ target << '--' if delim
981
+ opts.each { |val| target << (escape ? shell_escape(val) : shell_quote(val)) }
844
982
  end
845
983
 
846
- def append_first(list, flag: true, equals: false, quote: false, **kwargs)
984
+ def append_hash(opts, target: @session)
985
+ out = target.to_s
986
+ opts.each do |key, val|
987
+ key = key.to_s
988
+ key = "--#{key}" unless key[0] == '-'
989
+ next if out =~ Regexp.new(" #{key}(?: |=|$)")
990
+
991
+ case val
992
+ when String
993
+ append_repeat(key, [val], target: target)
994
+ when Array
995
+ append_repeat(key, val, target: target)
996
+ when Numeric
997
+ target << (key + (key.start_with?('--') ? '=' : '') + val)
998
+ when false
999
+ target << key.sub(/^--(?!no-)/, '--no-')
1000
+ else
1001
+ target << key
1002
+ end
1003
+ end
1004
+ end
1005
+
1006
+ def append_first(list, target: @session, flag: true, equals: false, quote: false, escape: true, **kwargs)
847
1007
  list.each do |opt|
848
1008
  next unless (val = option(opt, **kwargs))
849
1009
 
850
- return @session << (if flag
851
- shell_option(opt, equals ? val : nil, quote: quote)
852
- else
853
- shell_quote(val)
854
- end)
1010
+ return target << (if flag
1011
+ shell_option(opt, equals ? val : nil, quote: quote, escape: escape)
1012
+ else
1013
+ shell_quote(val)
1014
+ end)
855
1015
  end
856
1016
  end
857
1017
 
858
- def append_option(list, equals: false, quote: false, **kwargs)
1018
+ def append_option(list, target: @session, equals: false, quote: false, escape: true, **kwargs)
859
1019
  list.each do |flag|
860
1020
  next unless (val = option(flag, **kwargs))
861
1021
 
862
- @session << shell_option(flag, equals ? val : nil, quote: quote)
1022
+ target << shell_option(flag, equals ? val : nil, quote: quote, escape: escape)
863
1023
  end
864
1024
  end
865
1025
 
866
- def append_nocolor(flag = nil)
867
- @session << '--no-color' if flag || !ARG[:COLOR] || stdin?
1026
+ def append_nocolor(target: @session)
1027
+ target << '--no-color' if !ARG[:COLOR] || stdin? || option('no-color', ignore: false)
868
1028
  end
869
1029
 
870
- def guard_params(action, flag, args: nil, key: nil, pat: nil)
1030
+ def param_guard(action, flag, args: nil, key: nil, pat: nil)
871
1031
  if args && key
872
1032
  val = args[key]
873
1033
  return val unless val.nil? || (pat && !val.match?(pat))
@@ -918,17 +1078,48 @@ module Squared
918
1078
  ret && !ret.empty? ? ret : [val]
919
1079
  end
920
1080
 
921
- def pwd_set(done = nil, pass: false, &blk)
1081
+ def colormap(val)
1082
+ val.compact.map { |s| color(s) }.flatten
1083
+ end
1084
+
1085
+ def on(event, action, *args, **kwargs)
1086
+ return unless (obj = @events[event][action])
1087
+
1088
+ if obj.is_a?(Array) && obj[1].is_a?(Hash)
1089
+ opts = kwargs.empty? ? obj[1] : obj[1].merge(kwargs)
1090
+ target = obj[0]
1091
+ else
1092
+ opts = kwargs
1093
+ target = obj
1094
+ end
1095
+ as_a(target, flat: true).each do |cmd|
1096
+ case cmd
1097
+ when Proc, Method
1098
+ cmd.call(*args, **opts)
1099
+ when String
1100
+ run_s(cmd, **opts)
1101
+ end
1102
+ end
1103
+ end
1104
+
1105
+ def pwd_set(done = nil, pass: false, from: nil, &blk)
922
1106
  pwd = Pathname.pwd
923
1107
  if block_given?
924
- if path == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join >= RUBY_VERSION)
925
- ret = instance_eval(&blk)
1108
+ begin
1109
+ if path == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join >= RUBY_VERSION)
1110
+ ret = instance_eval(&blk)
1111
+ else
1112
+ Dir.chdir(path)
1113
+ ret = instance_eval(&blk)
1114
+ Dir.chdir(pwd)
1115
+ end
1116
+ rescue StandardError => e
1117
+ log.error e
1118
+ ret = on(:error, from, e) if from
1119
+ raise if exception && ret != true
926
1120
  else
927
- Dir.chdir(path)
928
- ret = instance_eval(&blk)
929
- Dir.chdir(pwd)
1121
+ ret
930
1122
  end
931
- ret
932
1123
  elsif @pwd == pwd
933
1124
  @pwd = nil
934
1125
  pwd unless done
@@ -973,6 +1164,26 @@ module Squared
973
1164
  end
974
1165
  end
975
1166
 
1167
+ def task_build(keys)
1168
+ namespace name do
1169
+ keys.each do |key|
1170
+ next unless workspace.task_include?(self, key)
1171
+
1172
+ action = workspace.series.name_get(key)
1173
+ unless @pass.include?(key.to_s) || workspace.task_defined?(name, action)
1174
+ workspace.task_desc(@desc, action)
1175
+ task action do
1176
+ __send__(key)
1177
+ end
1178
+ end
1179
+ next if (items = @children.select { |item| item.task_include?(key) }).empty?
1180
+
1181
+ workspace.task_desc(@desc, action, 'workspace')
1182
+ task task_join(action, 'workspace') => items.map { |item| task_join(item.name, action) }
1183
+ end
1184
+ end
1185
+ end
1186
+
976
1187
  def projectpath?(val)
977
1188
  Pathname.new(val).absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?('..')
978
1189
  end
@@ -1001,7 +1212,7 @@ module Squared
1001
1212
  end
1002
1213
 
1003
1214
  def invoked_sync?(action, val = nil)
1004
- return true if !val.nil? || from_sync?(ac = workspace.task_name(action))
1215
+ return true if val || from_sync?(ac = workspace.task_name(action))
1005
1216
  return val if group && !(val = from_sync?(ac, group)).nil?
1006
1217
  return val if (base = workspace.find_base(self)) && !(val = from_sync?(ac, base.ref)).nil?
1007
1218
 
@@ -1009,7 +1220,7 @@ module Squared
1009
1220
  true
1010
1221
  else
1011
1222
  val = workspace.series.name_get(action)
1012
- val == action ? false : invoked_sync?(val)
1223
+ val != action && invoked_sync?(val)
1013
1224
  end
1014
1225
  end
1015
1226