squared 0.1.3 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -350,35 +368,46 @@ module Squared
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(@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(@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
408
  as_a(@clean).each do |val|
380
- if (val = val.to_s) =~ %r{[\\/]$}
381
- dir = Pathname.new(val)
409
+ if val =~ %r{[\\/]$}
410
+ dir = Pathname.new(val.to_s)
382
411
  dir = basepath(dir) unless dir.absolute?
383
412
  next unless dir.directory?
384
413
 
@@ -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