squared 0.0.10 → 0.0.12

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.
@@ -8,21 +8,22 @@ module Squared
8
8
  module Workspace
9
9
  module Project
10
10
  class Base
11
- include Common
12
- include Format
11
+ include Common::Format
13
12
  include System
14
13
  include Shell
15
- include Task
16
14
  include Utils
17
15
  include ::Rake::DSL
18
16
 
19
17
  SEM_VER = /(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?/.freeze
20
18
 
21
- VAR_SET = %i[parent global envname theme run script env depend doc test copy clean].freeze
19
+ VAR_SET = %i[parent global envname dependfile theme run script env depend graph doc test copy clean].freeze
22
20
  private_constant :VAR_SET
23
21
 
24
22
  class << self
25
23
  def populate(*); end
24
+ def batchargs(*); end
25
+ def aliasargs(*); end
26
+ def bannerargs(*); end
26
27
 
27
28
  def tasks
28
29
  [].freeze
@@ -42,32 +43,39 @@ module Squared
42
43
  end
43
44
 
44
45
  def to_s
45
- super.to_s.match(/[^:]+$/)[0]
46
+ super.match(/[^:]+$/)[0]
47
+ end
48
+
49
+ def config?(*)
50
+ false
46
51
  end
47
52
  end
48
53
 
54
+ @@tasks = {
55
+ "#{ref}": {
56
+ graph: %i[run print].freeze
57
+ }.freeze
58
+ }
49
59
  @@print_order = 0
50
- @@tasks = {}
51
60
 
52
- attr_reader :name, :project, :workspace, :group, :path, :theme, :parent
53
- attr_accessor :exception, :pipe, :verbose
61
+ attr_reader :name, :project, :workspace, :path, :exception, :pipe, :theme, :group, :parent,
62
+ :dependfile, :verbose
54
63
 
55
- def initialize(name, path, workspace, *,
56
- group: nil, verbose: nil, pass: nil, exclude: nil,
57
- common: Common::KEY[:COMMON], pipe: Common::KEY[:PIPE], **kwargs)
58
- @name = name.to_s
64
+ def initialize(workspace, path, name, *, group: nil, graph: nil, pass: nil, exclude: nil,
65
+ common: ARG[:COMMON], **kwargs)
59
66
  @path = path
60
- @project = @path.basename.to_s
61
67
  @workspace = workspace
62
- @group = group&.to_s
68
+ @name = name.to_s.freeze
69
+ @project = @path.basename.to_s.freeze
70
+ @group = group&.to_s.freeze
63
71
  @depend = kwargs[:depend]
64
72
  @doc = kwargs[:doc]
65
73
  @test = kwargs[:test]
66
74
  @copy = kwargs[:copy]
67
75
  @clean = kwargs[:clean]
68
- @exception = workspace.exception
69
- @pipe = env_pipe(pipe, workspace.pipe)
70
- @verbose = verbose.nil? ? workspace.verbose : verbose
76
+ @exception = kwargs.key?(:exception) ? env_bool(kwargs[:exception]) : workspace.exception
77
+ @pipe = kwargs.key?(:pipe) ? env_pipe(kwargs[:pipe]) : workspace.pipe
78
+ @verbose = kwargs.key?(:verbose) ? kwargs[:verbose] : workspace.verbose
71
79
  @theme = if !@verbose
72
80
  {}
73
81
  elsif common
@@ -78,12 +86,16 @@ module Squared
78
86
  @output = []
79
87
  @ref = []
80
88
  @children = []
89
+ @graph = if graph
90
+ as_a(graph, workspace.prefix ? ->(val) { workspace.task_name(val).to_sym } : :to_sym).freeze
91
+ end
81
92
  @pass = (pass ? as_a(pass, :to_sym) : []).freeze
82
93
  @exclude = (exclude ? as_a(exclude, :to_sym) : []).freeze
83
- @envname = @name.gsub(/[^\w]+/, '_').upcase
84
- @desc = @name.split(':').join(' => ')
94
+ @envname = @name.gsub(/[^\w]+/, '_').upcase.freeze
95
+ @desc = (@name.include?(':') ? @name.split(':').join(ARG[:SPACE]) : @name).freeze
96
+ @parent = nil
85
97
  @global = false
86
- run_set kwargs[:run], kwargs[:env]
98
+ run_set(kwargs[:run], kwargs[:env], opts: kwargs.fetch(:opts, true))
87
99
  initialize_ref(Base.ref)
88
100
  end
89
101
 
@@ -93,7 +105,7 @@ module Squared
93
105
 
94
106
  def initialize_build(ref, **kwargs)
95
107
  initialize_ref(ref)
96
- if (@script = @workspace.script(group: @group, ref: ref))
108
+ if (@script = @workspace.script_get(group: @group, ref: ref))
97
109
  if @script[:log] && !kwargs.key?(:log)
98
110
  kwargs[:log] = @script[:log]
99
111
  @log = nil
@@ -107,7 +119,7 @@ module Squared
107
119
  initialize_logger(**kwargs)
108
120
  return if @output[0] == false
109
121
 
110
- data = @workspace.script(*@ref, @group)
122
+ data = @workspace.script_find(*@ref, @group)
111
123
  if @output[0].nil?
112
124
  if (scr = data[:script])
113
125
  @global = true
@@ -140,8 +152,8 @@ module Squared
140
152
  return if @log
141
153
 
142
154
  log = log.is_a?(::Hash) ? log.dup : { file: log }
143
- if (file = env('LOG_FILE')).nil? && (auto = env('LOG_AUTO'))
144
- file = case auto
155
+ unless (file = env('LOG_FILE'))
156
+ file = case env('LOG_AUTO')
145
157
  when 'y', 'year'
146
158
  "#{@name}-#{Date.today.year}.log"
147
159
  when 'm', 'month'
@@ -152,7 +164,7 @@ module Squared
152
164
  end
153
165
  if file ||= log[:file]
154
166
  file = Date.today.strftime(file)
155
- file = (dir = env('LOG_DIR')) ? Pathname.new(dir).join(file) : Pathname.new(file)
167
+ file = (dir = env('LOG_DIR')) ? @workspace.home.join(dir, file) : @workspace.home.join(file)
156
168
  begin
157
169
  file = file.realdirpath
158
170
  rescue StandardError => e
@@ -163,7 +175,7 @@ module Squared
163
175
  end
164
176
  end
165
177
  log[:progname] ||= @name
166
- if (val = env('LOG_LEVEL', ignore: nil))
178
+ if (val = env('LOG_LEVEL', ignore: false))
167
179
  log[:level] = val
168
180
  end
169
181
  log.delete(:file)
@@ -171,10 +183,14 @@ module Squared
171
183
  end
172
184
 
173
185
  def initialize_env(dev: nil, prod: nil, **)
174
- prefix = "BUILD_#{@envname}"
175
- @dev = env_match("#{prefix}_DEV", dev)
176
- @prod = env_match("#{prefix}_PROD", prod)
177
- if (val = env('BUILD', suffix: 'ENV'))
186
+ pre = "BUILD_#{@envname}"
187
+ @dev = env_match("#{pre}_DEV", dev)
188
+ @prod = env_match("#{pre}_PROD", prod)
189
+ cmd = @output[0]
190
+ unless cmd == false || cmd.is_a?(::Array) || (val = env('BUILD', suffix: 'OPTS')).nil?
191
+ @output[cmd ? 1 : 3] = shell_split(val, join: true)
192
+ end
193
+ unless @output[2] == false || (val = env('BUILD', suffix: 'ENV')).nil?
178
194
  begin
179
195
  data = JSON.parse(val)
180
196
  raise_error('invalid JSON object', val, hint: "#{prefix}_ENV") unless data.is_a?(::Hash)
@@ -186,10 +202,12 @@ module Squared
186
202
  return unless (val = env('BUILD', strict: true))
187
203
 
188
204
  @global = false
189
- if script?
205
+ if val == '0'
206
+ @output = [false]
207
+ elsif script?
190
208
  script_set val
191
209
  else
192
- run_set(val, opts: false)
210
+ run_set val
193
211
  end
194
212
  end
195
213
 
@@ -198,36 +216,90 @@ module Squared
198
216
  end
199
217
 
200
218
  def populate(*)
201
- check = lambda do |proj, key|
202
- workspace.series.include?(key) ? proj.has?(key, Base.ref) : workspace.task_extend?(proj, key)
203
- end
204
-
205
219
  namespace name do
206
220
  workspace.series.each_key do |key|
207
- next unless check.(self, key)
221
+ next unless workspace.task_include?(self, key)
208
222
 
209
- unless workspace.task_defined?("#{name}:#{key}")
210
- desc message(@desc, key)
211
- task key do
223
+ s = workspace.series.name_get(key)
224
+ unless workspace.task_defined?(name, s)
225
+ desc message(@desc, s)
226
+ task s do
212
227
  __send__(key)
213
228
  end
214
229
  end
215
- next if (items = @children.select { |item| check.(item, key) }).empty?
230
+ next if (items = @children.select { |item| workspace.task_include?(item, key) }).empty?
216
231
 
217
- desc message(@desc, key, 'workspace')
218
- task "#{key}:workspace" => items.map { |item| "#{item.name}:#{key}" }
232
+ desc message(@desc, s, 'workspace')
233
+ task task_join(s, 'workspace') => items.map { |item| task_join(item.name, s) }
234
+ end
235
+ next unless ref?(Base.ref)
236
+
237
+ @@tasks[Base.ref].each do |action, flags|
238
+ namespace action do
239
+ flags.each do |flag|
240
+ case action
241
+ when :graph
242
+ next unless graph?
243
+
244
+ if flag == :run
245
+ desc format_desc(action, flag, 'pass*')
246
+ task flag, [:pass] do |_, args|
247
+ graph(pass: args.to_a)
248
+ end
249
+ else
250
+ desc format_desc(action, flag)
251
+ task flag do
252
+ out = graph(out: [])
253
+ emphasize(out, title: path, right: true, border: borderstyle,
254
+ footer: "dependencies: #{out.size - 1}",
255
+ sub: [
256
+ { pat: /^(#{Regexp.escape(path.to_s)})(.*)$/, styles: theme[:header] },
257
+ { pat: /^(#{Regexp.escape(name)})(.*)$/, styles: theme[:active] },
258
+ { pat: /^(\s*)([a-z]+: )(\d+)$/, styles: theme[:inline], index: 3 }
259
+ ])
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
219
265
  end
220
266
  end
221
267
  end
222
268
 
223
- def add(path, name = nil, **kwargs, &blk)
224
- return self unless source_path?(path = base_path(path))
269
+ def with(**kwargs, &blk)
270
+ @withargs = kwargs.empty? ? nil : kwargs
271
+ if block_given?
272
+ instance_eval(&blk)
273
+ @withargs = nil
274
+ end
275
+ self
276
+ end
225
277
 
278
+ def add(path, name = nil, **kwargs, &blk)
279
+ if path.is_a?(::String) && (data = %r{^(.+)[\\/]\*+$}.match(path))
280
+ path = basepath(data[1]).children.select(&:directory?)
281
+ end
282
+ if path.is_a?(::Array)
283
+ name = @name if name == true
284
+ path.each { |val| add(val, name && task_join(name, File.basename(val)), **kwargs, &blk) }
285
+ return self
286
+ elsif !projectpath?(path = basepath(path))
287
+ return self
288
+ elsif name.is_a?(::Symbol)
289
+ name = name.to_s
290
+ elsif !name.is_a?(::String)
291
+ name = nil
292
+ end
293
+ if @withargs
294
+ data = @withargs.dup
295
+ data.merge!(kwargs)
296
+ kwargs = data
297
+ end
226
298
  kwargs[:group] = group unless kwargs.key?(:group)
227
299
  kwargs[:ref] = ref unless kwargs.key?(:ref)
228
300
  parent = self
229
301
  proj = nil
230
- workspace.add(path, name || path.basename.to_s, **kwargs) do
302
+ workspace.add(path, name || path.basename, **kwargs) do
231
303
  variable_set :parent, parent
232
304
  proj = self
233
305
  end
@@ -236,54 +308,49 @@ module Squared
236
308
  self
237
309
  end
238
310
 
239
- def build(*args, sync: true)
311
+ def build(*args, sync: invoked_sync?('build'), **)
240
312
  if args.empty?
241
- cmd, opts, var = @output
242
- opts &&= shell_split(opts, join: true)
313
+ cmd, opts, var, flags = @output
314
+ banner = verbose == 1
243
315
  else
244
316
  cmd = args.shift
245
317
  opts = args.map { |val| shell_quote(val, force: false) }.join(' ')
318
+ banner = verbose
246
319
  end
247
320
  if cmd
248
- if opts
321
+ case opts
322
+ when ::String
249
323
  cmd = "#{cmd} #{opts}"
250
- elsif cmd.is_a?(::Array)
324
+ when ::Array
251
325
  cmd = cmd.join(' && ')
326
+ else
327
+ cmd = [cmd, compose(opts, script: false)].compact.join(' ') unless opts == false || !respond_to?(:compose)
252
328
  end
253
- banner = verbose != false
254
329
  else
255
330
  return unless respond_to?(:compose)
256
331
 
257
- cmd = compose(opts)
258
- banner = verbose == 1
332
+ cmd = compose(opts, flags, script: true)
259
333
  end
260
334
  run(cmd, var, banner: banner, sync: sync)
261
335
  end
262
336
 
263
- def refresh(*)
264
- build(sync: invoked_sync?('refresh') || invoked_sync?('build'))
265
- return if run_task "#{name}:copy"
266
-
267
- copy if copy?
337
+ def depend(*, sync: invoked_sync?('depend'), **)
338
+ run(@depend, sync: sync) if @depend
268
339
  end
269
340
 
270
- def depend(*)
271
- run(@depend, sync: invoked_sync?('depend')) if @depend
341
+ def copy(*, sync: invoked_sync?('copy'), **)
342
+ run_s(@copy, sync: sync) if @copy
272
343
  end
273
344
 
274
- def copy(*)
275
- run_s(@copy, sync: invoked_sync?('copy')) if @copy
345
+ def doc(*, sync: invoked_sync?('doc'), **)
346
+ build(@doc, sync: sync) if @doc
276
347
  end
277
348
 
278
- def doc
279
- build(@doc, sync: invoked_sync?('doc')) if @doc
349
+ def test(*, sync: invoked_sync?('test'), **)
350
+ build(@test, sync: sync) if @test
280
351
  end
281
352
 
282
- def test
283
- build(@test, sync: invoked_sync?('test')) if @test
284
- end
285
-
286
- def clean
353
+ def clean(*)
287
354
  case @clean
288
355
  when ::String
289
356
  run_s(@clean, sync: invoked_sync?('clean'))
@@ -291,13 +358,13 @@ module Squared
291
358
  as_a(@clean).each do |val|
292
359
  if (val = val.to_s) =~ %r{[\\/]$}
293
360
  dir = Pathname.new(val)
294
- dir = base_path(dir) unless dir.absolute?
361
+ dir = basepath(dir) unless dir.absolute?
295
362
  next unless dir.directory?
296
363
 
297
364
  log.warn "rm -rf #{dir}"
298
365
  dir.rmtree(verbose: true)
299
366
  else
300
- files = val.include?('*') ? Dir[base_path(val)] : [base_path(val)]
367
+ files = val.include?('*') ? Dir[basepath(val)] : [basepath(val)]
301
368
  files.each do |file|
302
369
  begin
303
370
  File.delete(file) if File.file?(file)
@@ -310,16 +377,29 @@ module Squared
310
377
  end
311
378
  end
312
379
 
380
+ def graph(*, sync: invoked_sync?('graph'), pass: [], out: nil)
381
+ data = graph_collect(self)
382
+ data[name] << self unless out
383
+ run_graph(data, name, out, sync: sync, pass: pass)
384
+ out
385
+ end
386
+
313
387
  def log
314
- if @log.is_a?(::Array)
315
- @log = Logger.new(enabled? ? @log[0] : nil, **@log[1])
316
- else
317
- @log
318
- end
388
+ return @log unless @log.is_a?(::Array)
389
+
390
+ @log = Logger.new(enabled? ? @log[0] : nil, **@log[1])
319
391
  end
320
392
 
321
- def base_path(*args)
322
- path.join(*args)
393
+ def basepath(*args, ascend: nil)
394
+ ret = path.join(*args)
395
+ return ret unless ascend && !ret.exist?
396
+
397
+ path.parent.ascend.each do |dir|
398
+ target = dir.join(*args)
399
+ return target if target.exist?
400
+ break if (ascend.is_a?(String) && dir.join(ascend).exist?) || workspace.root == dir || parent&.path == dir
401
+ end
402
+ ret
323
403
  end
324
404
 
325
405
  def color(val)
@@ -328,26 +408,43 @@ module Squared
328
408
  end
329
409
 
330
410
  def variable_set(key, *val, **kwargs)
331
- if variable_all.include?(key)
411
+ if variables.include?(key)
332
412
  case key
333
413
  when :run
334
- run_set(*val, opts: false, **kwargs)
414
+ run_set(*val, **kwargs)
335
415
  when :script
336
416
  script_set(*val, **kwargs)
337
417
  when :env
338
- run_set(output[0], *val, opts: false, **kwargs)
418
+ run_set(output[0], *val, **kwargs)
419
+ when :parent
420
+ @parent = val if (val = val.first).is_a?(Project::Base)
421
+ when :graph
422
+ @graph = case val.first
423
+ when nil, false
424
+ nil
425
+ else
426
+ val.flatten.map!(&:to_s).freeze
427
+ end
428
+ when :dependfile
429
+ @dependfile = basepath(*val)
339
430
  else
340
431
  instance_variable_set :"@#{key}", val.first
341
432
  end
342
433
  else
343
- log.warn "variable_set: @#{key} (not defined)"
434
+ log.warn "variable_set: @#{key} (private)"
344
435
  end
345
436
  end
346
437
 
347
- def variable_all
348
- VAR_SET
438
+ def allref
439
+ @ref.reverse_each
349
440
  end
350
441
 
442
+ def dependtype(*)
443
+ @dependindex ? @dependindex.succ : 0
444
+ end
445
+
446
+ def version(*); end
447
+
351
448
  def inspect
352
449
  "#<#{self.class}: #{name} => #{self}>"
353
450
  end
@@ -384,12 +481,16 @@ module Squared
384
481
  @output[0].nil? && !!@output[1] && respond_to?(:compose)
385
482
  end
386
483
 
387
- def refresh?
388
- build? && copy?
484
+ def depend?
485
+ !!@depend
389
486
  end
390
487
 
391
- def depend?
392
- @depend != false && (!@depend.nil? || has?('outdated'))
488
+ def graph?
489
+ @graph.is_a?(::Array) && !@graph.empty?
490
+ end
491
+
492
+ def copy?
493
+ runnable?(@copy) || workspace.task_defined?(name, 'copy')
393
494
  end
394
495
 
395
496
  def doc?
@@ -400,12 +501,8 @@ module Squared
400
501
  !!@test
401
502
  end
402
503
 
403
- def copy?
404
- runnable?(@copy) || workspace.task_defined?("#{name}:copy")
405
- end
406
-
407
504
  def clean?
408
- runnable?(@clean) || workspace.task_defined?("#{name}:clean")
505
+ runnable?(@clean) || workspace.task_defined?(name, 'clean')
409
506
  end
410
507
 
411
508
  def dev?
@@ -418,18 +515,22 @@ module Squared
418
515
 
419
516
  private
420
517
 
421
- def run(cmd = @session, var = nil, exception: @exception, banner: true, sync: true, req: nil, **)
422
- if req && !base_path(req).exist?
423
- log.warn "#{req} (not found)"
424
- return
425
- end
426
- cmd = close_session(cmd)
518
+ def variables
519
+ VAR_SET
520
+ end
521
+
522
+ def puts(*args)
523
+ puts_oe(*args, pipe: pipe)
524
+ end
525
+
526
+ def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, **)
527
+ cmd = session_done(cmd)
427
528
  log.info cmd
428
529
  begin
429
530
  if cmd =~ /^\S+:(\S+:?)+$/ && workspace.task_defined?(cmd)
430
531
  print_item if sync
431
532
  log.warn "ENV was discarded: #{var}" if var
432
- invoke(cmd, exception: exception, warning: warning?)
533
+ task_invoke(cmd, exception: exception, warning: warning?)
433
534
  else
434
535
  print_item format_banner(cmd, banner: banner) if sync
435
536
  args = var.is_a?(::Hash) ? [var, cmd] : [cmd]
@@ -441,52 +542,114 @@ module Squared
441
542
  end
442
543
  end
443
544
 
444
- def run_s(cmd, **kwargs)
445
- as_a(cmd).each { |val| run(val, banner: !!verbose, **kwargs) if val.is_a?(::String) }
545
+ def run_s(*cmd, env: nil, banner: verbose != false, **kwargs)
546
+ cmd.each { |val| run(val, env, banner: banner, **kwargs) }
446
547
  end
447
548
 
448
- def run_task(key)
449
- return false unless workspace.task_defined?(key)
549
+ def run_graph(data, start, out = nil, sync: true, pass: [], done: [], depth: 0, last: false)
550
+ check = ->(deps) { deps.reject { |val| done.include?(val) } }
551
+ items = check.(data[start])
552
+ if out
553
+ a, b, c, d, e = ARG[:GRAPH]
554
+ out << case depth
555
+ when 0
556
+ start
557
+ when 1
558
+ if items.empty?
559
+ "#{d}#{b * 4} #{start}"
560
+ else
561
+ "#{last ? d : c}#{b * 3}#{e} #{start}"
562
+ end
563
+ else
564
+ "#{a}#{' ' * (depth - 1)}#{d}#{b * 2}#{items.empty? ? b : e} #{start}"
565
+ end
566
+ end
567
+ items.each_with_index do |proj, i|
568
+ next if done.include?(proj)
450
569
 
451
- invoke(key, exception: exception, warning: warning?)
452
- true
453
- end
570
+ j = out ? i == items.size - 1 || check.(items[i + 1..-1]).empty? : false
571
+ s = proj.name
572
+ unless start == s || (none = check.(data.fetch(s, [])).empty?)
573
+ run_graph(data, s, out, sync: sync, pass: pass, done: done, depth: depth.succ, last: j)
574
+ end
575
+ if !out
576
+ if (script = workspace.script_get(group: proj.group, ref: proj.ref))
577
+ tasks = script[:graph]
578
+ end
579
+ (tasks || (dev? ? %w[build copy] : %w[depend build])).each do |meth|
580
+ next if pass.include?(meth)
454
581
 
455
- def env(key, default = nil, equals: nil, ignore: ['0'].freeze, suffix: nil, strict: false)
456
- a = "#{key}_#{@envname}"
457
- b = ''
458
- if suffix
459
- a = "#{a}_#{suffix}"
460
- elsif !strict
461
- b = ENV.fetch(key, '')
582
+ if workspace.task_defined?(cmd = task_join(s, meth))
583
+ run(cmd, sync: false, banner: false)
584
+ elsif proj.has?(meth)
585
+ proj.__send__(meth.to_sym, sync: sync)
586
+ end
587
+ end
588
+ elsif none
589
+ a, b, c, d = ARG[:GRAPH]
590
+ out << if depth == 0
591
+ "#{i == items.size - 1 ? d : c}#{b * 4} #{s}"
592
+ else
593
+ "#{last && depth == 1 ? ' ' : a}#{' ' * depth}#{j ? d : c}#{b * 3} #{s}"
594
+ end
595
+ end
596
+ done << proj
462
597
  end
463
- ret = ENV.fetch(a, b)
464
- return ret == equals.to_s unless equals.nil?
598
+ end
599
+
600
+ def graph_collect(target, data: {})
601
+ deps = []
602
+ target.instance_variable_get(:@graph)&.each do |val|
603
+ next unless (proj = workspace.find(name: val))
465
604
 
466
- ret.empty? || as_a(ignore).any? { |val| ret == val.to_s } ? default : ret
605
+ deps << proj
606
+ graph_collect(proj, data: data) if proj.graph? && !data.key?(proj.name)
607
+ deps += data.fetch(val, [])
608
+ end
609
+ data[target.name] = deps.uniq.reject { |proj| proj == target }
610
+ data
467
611
  end
468
612
 
469
- def puts(*args)
470
- pipe == 2 ? $stderr.puts(*args) : $stdout.puts(*args)
613
+ def env(key, default = nil, suffix: nil, equals: nil, ignore: nil, strict: false)
614
+ a = "#{key}_#{@envname}"
615
+ ret = if suffix
616
+ ENV.fetch("#{a}_#{suffix}", '')
617
+ else
618
+ ignore = ['0'].freeze if ignore.nil? && !strict
619
+ ENV[a] || (strict ? '' : ENV.fetch(key, ''))
620
+ end
621
+ return ret == equals.to_s unless equals.nil?
622
+
623
+ ret.empty? || (ignore && as_a(ignore).any? { |val| ret == val.to_s }) ? default : ret
471
624
  end
472
625
 
473
- def session(*cmd, prefix: nil)
474
- if (val = ENV["#{(prefix || cmd.first).upcase}_OPTIONS"])
626
+ def session(*cmd, prefix: cmd.first)
627
+ if (val = env("#{prefix.upcase}_OPTIONS"))
475
628
  split_escape(val).each { |opt| cmd << fill_option(opt) }
476
629
  end
477
630
  @session = JoinSet.new(cmd)
478
631
  end
479
632
 
480
- def close_session(cmd)
481
- return cmd unless cmd.respond_to?(:done)
633
+ def session_done(cmd)
634
+ return cmd.to_s unless cmd.respond_to?(:done)
482
635
 
483
636
  raise_error('no args were added', hint: cmd.first || name) unless cmd.size > 1
484
637
  @session = nil if cmd == @session
485
638
  cmd.done
486
639
  end
487
640
 
641
+ def option(*args, prefix: @session&.first, **kwargs)
642
+ if prefix
643
+ args.each do |val|
644
+ ret = env("#{prefix}_#{val.gsub(/\W/, '_')}".upcase, **kwargs)
645
+ return ret if ret
646
+ end
647
+ end
648
+ nil
649
+ end
650
+
488
651
  def print_item(*val)
489
- puts unless @@print_order == 0 || stdin?
652
+ puts if @@print_order > 0 && verbose && !stdin?
490
653
  @@print_order += 1
491
654
  puts val unless val.empty? || (val.size == 1 && val.first.nil?)
492
655
  end
@@ -497,7 +660,7 @@ module Squared
497
660
  if styles.any? { |s| s.to_s.end_with?('!') }
498
661
  pad = 1
499
662
  elsif !client && styles.size <= 1
500
- styles = [:bold] + styles
663
+ styles = %i[bold] + styles
501
664
  end
502
665
  end
503
666
  n = Project.max_width(lines)
@@ -516,33 +679,37 @@ module Squared
516
679
  out.join("\n")
517
680
  end
518
681
 
519
- def print_footer(*lines, sub: nil, border: theme[:border], reverse: false)
682
+ def print_footer(*lines, sub: nil, reverse: false, right: false, **kwargs)
683
+ border = kwargs.key?(:border) ? kwargs[:border] : borderstyle
520
684
  n = Project.max_width(lines)
521
685
  sub = as_a(sub)
522
686
  lines.map! do |val|
523
- s = val.ljust(n)
687
+ s = right ? val.rjust(n) : val.ljust(n)
524
688
  sub.each { |h| s = sub_style(s, **h) }
525
689
  s
526
690
  end
527
- ret = [sub_style('-' * n, styles: border), *lines]
691
+ ret = [sub_style('-' * n, styles: border || theme[:border]), *lines]
528
692
  ret.reverse! if reverse
529
693
  ret.join("\n")
530
694
  end
531
695
 
532
- def format_desc(action, flag, opts = nil, req: '', arg: 'opts*')
696
+ def format_desc(action, flag, opts = nil, req: nil, arg: 'opts*')
533
697
  opts = "#{arg}=#{opts.join(',')}" if opts.is_a?(::Array)
534
- unless flag
698
+ out = [@desc]
699
+ if flag
700
+ out << action
701
+ else
535
702
  flag = action
536
- action = ''
537
703
  end
538
- req = opts ? "#{req}," : "[#{req}]" unless req.to_s.empty?
539
- message(@desc, action, opts ? "#{flag}[#{req}#{opts}]" : flag.to_s + req)
704
+ req &&= opts ? "#{req}," : "[#{req}]"
705
+ out << (opts ? "#{flag}[#{req}#{opts}]" : "#{flag}#{req}")
706
+ message(*out)
540
707
  end
541
708
 
542
- def format_banner(cmd, banner: true, multiple: false)
543
- return unless banner
709
+ def format_banner(cmd, banner: true)
710
+ return unless banner && ARG[:BANNER]
544
711
 
545
- if (data = workspace.banner(group: group, ref: ref))
712
+ if (data = workspace.banner_get(*@ref, group: group))
546
713
  client = true
547
714
  else
548
715
  data = { command: true, order: %i[path], styles: theme[:banner], border: theme[:border] }
@@ -550,13 +717,62 @@ module Squared
550
717
  if verbose
551
718
  out = []
552
719
  out << cmd.sub(/^\S+/, &:upcase) if data[:command]
553
- data[:order].each { |val| out << val.to_s if (val = __send__(val)) }
720
+ data[:order].each do |val|
721
+ if val.is_a?(::Array)
722
+ s = ' '
723
+ found = false
724
+ val = val.map do |meth|
725
+ if meth.is_a?(::String)
726
+ s = ''
727
+ meth
728
+ elsif respond_to?(meth)
729
+ found = true
730
+ __send__(meth)
731
+ end
732
+ end
733
+ val = val.compact.join(s)
734
+ next unless found && !val.empty?
735
+ elsif (val = __send__(val)).nil?
736
+ next
737
+ end
738
+ out << val.to_s
739
+ end
554
740
  print_banner(*out, styles: data[:styles], border: data[:border], client: client)
555
741
  elsif multiple && workspace.series.multiple?
556
742
  "## #{__send__(data[:order].first || :path)} ##"
557
743
  end
558
744
  end
559
745
 
746
+ def format_list(items, cmd, type, grep: [], from: nil, each: nil)
747
+ reg = grep.map { |val| Regexp.new(val) }
748
+ out = []
749
+ if (pad = items.size) > 0
750
+ pad = pad.to_s.size
751
+ items.each_with_index do |val, i|
752
+ next unless reg.empty? || reg.any? { |pat| pat.match?(val[0]) }
753
+
754
+ out << "#{(i + 1).to_s.rjust(pad)}. #{each ? each.(val) : val[0]}"
755
+ end
756
+ end
757
+ if out.empty?
758
+ out = ["No #{type} were found:", '']
759
+ unless grep.empty?
760
+ i = 0
761
+ out += grep.map { |s| "#{i += 1}. #{s}" }
762
+ out << ''
763
+ end
764
+ if from
765
+ out << from
766
+ pat = /^(#{Regexp.escape(out.last)})(.*)$/
767
+ end
768
+ else
769
+ pat = /^(\s*\d+\.)(.+)$/
770
+ end
771
+ sub = [headerstyle]
772
+ sub << { pat: pat, styles: theme[:active] } if pat
773
+ emphasize(out, title: task_join(name, cmd), border: borderstyle, sub: sub)
774
+ end
775
+
560
776
  def empty_status(msg, title, obj, always: false)
561
777
  "#{msg}#{!always && (!obj || obj == 0 || obj.to_s.empty?) ? '' : message(hint: message(title, obj.to_s))}"
562
778
  end
@@ -569,22 +785,22 @@ module Squared
569
785
  opts.each { |val| @session << val }
570
786
  end
571
787
 
572
- def append_nocolor
573
- @session << (!ENV.fetch('NO_COLOR', '').empty? || stdin? ? '--no-color' : '')
788
+ def append_nocolor(flag = nil)
789
+ @session << '--no-color' if flag || !ARG[:COLOR] || stdin?
574
790
  end
575
791
 
576
792
  def guard_params(action, flag, args: nil, key: nil, pat: nil)
577
793
  if args && key
578
- return unless (val = args[key]).nil? || (pat && !val.match?(pat))
794
+ val = args[key]
795
+ return val unless val.nil? || (pat && !val.match?(pat))
579
796
 
580
797
  @session = nil
581
798
  raise_error(action, "#{flag}[#{key}]", hint: val.nil? ? 'missing' : 'invalid')
582
- elsif args.is_a?(::Array)
583
- return unless args.empty?
584
-
799
+ elsif args.is_a?(::Array) && args.empty?
585
800
  @session = nil
586
- raise_error(action, "#{flag}[]", hint: 'empty')
801
+ raise_error(action, "#{flag}+", hint: 'empty')
587
802
  end
803
+ args
588
804
  end
589
805
 
590
806
  def pwd_set(done = nil, &blk)
@@ -612,39 +828,27 @@ module Squared
612
828
  end
613
829
  end
614
830
 
615
- def semver(val)
616
- unless val[3]
617
- val[3] = '.'
618
- val[4] = '0'
831
+ def run_set(cmd, val = nil, opts: nil, **)
832
+ unless @output[1] == false && !@output[0].nil?
833
+ if opts == false
834
+ @output[1] = false
835
+ elsif opts && opts != true
836
+ @output[1] = opts
837
+ end
619
838
  end
620
- unless val[1]
621
- val[1] = '.'
622
- val[2] = '0'
839
+ unless @output[2] == false
840
+ if val.is_a?(::Hash)
841
+ @output[2] = val
842
+ elsif val == false
843
+ @output[2] = false
844
+ end
623
845
  end
624
- val
625
- end
626
-
627
- def semmajor(cur, want)
628
- (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
629
- end
630
-
631
- def scriptargs
632
- { target: script? ? @output[1] : @output[0], ref: ref, group: group, global: @global }
633
- end
634
-
635
- def run_set(cmd, val = nil, *, opts: true, **)
636
846
  @output[0] = cmd
637
- @output[1] = if cmd && opts
638
- env('BUILD', opts.is_a?(::String) ? opts : nil, suffix: 'OPTS')
639
- end
640
- if val.is_a?(::Hash)
641
- @output[2] = val
642
- elsif val == false
643
- @output[2] = nil
644
- end
645
847
  end
646
848
 
647
- def script_set(cmd, *, prod: nil, **)
849
+ def script_set(cmd, prod: nil, **)
850
+ return if @output[1] == false && @output[0].nil?
851
+
648
852
  @output[0] = nil
649
853
  @output[1] = if @global && cmd.is_a?(::Array)
650
854
  cmd[prod == true ? 1 : 0]
@@ -653,16 +857,12 @@ module Squared
653
857
  end
654
858
  end
655
859
 
656
- def source_path?(val)
657
- Pathname.new(val).absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.match?(%r{^\.\.[/\\]})
860
+ def projectpath?(val)
861
+ Pathname.new(val).absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.start_with?('..')
658
862
  end
659
863
 
660
- def warning?
661
- workspace.warning
662
- end
663
-
664
- def stdin?
665
- pipe == 0
864
+ def semmajor?(cur, want)
865
+ (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
666
866
  end
667
867
 
668
868
  def runnable?(val)
@@ -676,23 +876,83 @@ module Squared
676
876
  end
677
877
  end
678
878
 
679
- def from_sync?(val)
680
- if invoked?(val)
681
- !workspace.task_defined?("#{val}:sync")
682
- elsif workspace.series.sync?("#{val}:sync")
879
+ def from_sync?(*val)
880
+ if task_invoked?(key = task_join(*val))
881
+ !workspace.task_defined?(key, 'sync')
882
+ elsif workspace.series.sync?(task_join(key, 'sync'))
683
883
  true
684
884
  end
685
885
  end
686
886
 
687
887
  def invoked_sync?(action, val = nil)
688
888
  return true if !val.nil? || from_sync?(ac = workspace.task_name(action))
889
+ return val if group && !(val = from_sync?(ac, group)).nil?
890
+ return val if (base = workspace.find_base(self)) && !(val = from_sync?(ac, base.ref)).nil?
689
891
 
690
- return val if group && !(val = from_sync?("#{ac}:#{group}")).nil?
691
- return val if (base = workspace.find_base(self)) && !(val = from_sync?("#{ac}:#{base.ref}")).nil?
892
+ if task_invoked?(task_join(name, ac)) && (!task_invoked?(ac) || !workspace.task_defined?(ac, 'sync'))
893
+ true
894
+ else
895
+ val = workspace.series.name_get(action)
896
+ val == action ? false : invoked_sync?(val)
897
+ end
898
+ end
899
+
900
+ def stdin?
901
+ pipe == 0
902
+ end
903
+
904
+ def warning?
905
+ workspace.warning
906
+ end
907
+
908
+ def projectmap(files, parent: false)
909
+ files = files.select { |val| projectpath?(val) } unless parent
910
+ files.map { |val| val == '.' ? '.' : shell_quote(basepath(val.strip)) }
911
+ end
912
+
913
+ def semver(val)
914
+ return val if val[3]
915
+
916
+ val[3] = '.'
917
+ val[4] = '0'
918
+ unless val[1]
919
+ val[1] = '.'
920
+ val[2] = '0'
921
+ end
922
+ val
923
+ end
924
+
925
+ def semscan(val)
926
+ val.scan(SEM_VER).first
927
+ end
692
928
 
693
- invoked?("#{name}:#{ac}") && (!invoked?(ac) || !workspace.task_defined?("#{ac}:sync"))
929
+ def borderstyle
930
+ if (data = workspace.banner_get(*@ref, group: group))
931
+ data[:border]
932
+ end
933
+ end
934
+
935
+ def headerstyle
936
+ { pat: /^(\S+)(\s+)$/, styles: theme[:header] }
937
+ end
938
+
939
+ def scriptargs
940
+ { target: script? ? @output[1] : @output[0], ref: ref, group: group, global: @global }
941
+ end
942
+
943
+ def indexdata(val)
944
+ if (data = /\A\^(\d+)(:.+)?\z/.match(val))
945
+ [data[1].to_i, data[2] ? data[2][1..-1] : nil]
946
+ end
947
+ end
948
+
949
+ def indexerror(val, list = nil)
950
+ raise_error("requested index #{val}", hint: list && "of #{list.size}")
694
951
  end
695
952
  end
953
+
954
+ Application.impl_project = Base
955
+ Application.attr_banner = Common::SymSet.new(%i[name project path ref group parent])
696
956
  end
697
957
  end
698
958
  end