squared 0.4.11 → 0.4.13

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa16edc572cc39bb0bef0b2b5ba04419f2994e824923b354182f9e60b6aee1b2
4
- data.tar.gz: 75a6b805d236d45f09b331bf62cbfcc5f9b043632d194474511763c7fc7fbf64
3
+ metadata.gz: 2bc10e49a42c1eda78b4ff20f734efffd74eda16bc8dae742d2a0f29c0f34851
4
+ data.tar.gz: 64b5e88317ba5208cc7824f40b9a4ff810a63197bc0b27813906a2293bbe1388
5
5
  SHA512:
6
- metadata.gz: 510b7807e4e2be74dfe8ba5202115e3b5cefbd27d94cb14af169007bcdedde7f8c8516679c1bc614cb633eb1a904a6309403346f7c0c8490ad13fa208f2bd1f0
7
- data.tar.gz: 3a124f06b78467a4eda6a1f34c2a80f6bb9f78867c5daefbcb0f628d9eb6a4bfbf0193e0d5509f38000e7e6ce6c8c3f4d112f0c0146260d30664692cda495186
6
+ metadata.gz: f34a4d13854343d0713e34bf964feda5206f1c9a60ec2781fca611e74f7257753e01a895e081663b0c2d154a4c4834c39fa478e4665c58982eea053b53fd9529
7
+ data.tar.gz: ff549d38162731add0bbb6dd8bd7566f2200d6006f92c268aa0af4271e4f6a60a5a407b6a3a48fbeeee035c0ede41c135f760eb907582ede90020a1c61c7d890
data/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.13] - 2025-06-16
4
+
5
+ ### Added
6
+
7
+ - Docker command image action tag was implemented.
8
+ - Docker command image action save was implemented.
9
+ - Docker command container action create was implemented.
10
+ - Node command exec with NPM and PNPM were implemented.
11
+ - Ruby command exec and config are interactive.
12
+ - Python command exec using Kernel.exec was implemented.
13
+ - Theme color "latest" for semver was created.
14
+ - Project can be sorted by graph using comparison operator.
15
+ - Project base command prereqs was created.
16
+ - Project setter methods for private variables with validation.
17
+
18
+ ### Changed
19
+
20
+ - Mismatched shell quoted options are fixed rather than wrapped.
21
+ - Python command venv action run was renamed exec.
22
+ - Ruby command file can accept a glob file pattern.
23
+
24
+ ### Fixed
25
+
26
+ - Shell option quote detection uses Regexp conditionals.
27
+ - Docker property registry did not detect nil values.
28
+ - Gem command exec did not include gem option flag.
29
+ - Project method variable_set did not call block.
30
+ - Project output divider was not printed when not verbose.
31
+
32
+ ## [0.4.12] - 2025-05-18
33
+
34
+ ### Added
35
+
36
+ - Git command stash with actions using index are interactive.
37
+ - Project tasks can be pipelined in separate thread groups.
38
+ - Global tasks can be pipelined with project tasks.
39
+ - Python venv module supports initialization options.
40
+
41
+ ### Changed
42
+
43
+ - Node command run parses recognized options per package manager.
44
+ - Project unpack uses merge with multiple headers.
45
+ - Invalid log directory does not abort program.
46
+ - Project accessor line_width was replaced with Rake equivalent.
47
+
48
+ ### Fixed
49
+
50
+ - Git pull option multiple was not detected.
51
+ - Git output used undefined method when displaying results.
52
+ - Workspace did not add prefix to duplicate project names.
53
+
54
+ ## [0.3.11] - 2025-05-15
55
+
56
+ - See `0.2.11`.
57
+
58
+ ## [0.2.11] - 2025-05-15
59
+
60
+ ### Fixed
61
+
62
+ - Disabled batch and alias tasks were not hidden.
63
+ - Workspace git did not parse multiple download URIs.
64
+
65
+ ## [0.1.8] - 2025-05-15
66
+
67
+ ### Fixed
68
+
69
+ - Disabled batch and alias tasks were not hidden.
70
+ - Log messages were written to terminal twice when emphasized.
71
+ - Node outdated interactive for major would sometimes deactivate.
72
+ - Node outdated interactive for major was mislabeled as minor.
73
+
3
74
  ## [0.4.11] - 2025-05-09
4
75
 
5
76
  ### Added
@@ -665,6 +736,8 @@
665
736
 
666
737
  - Changelog was created.
667
738
 
739
+ [0.4.13]: https://github.com/anpham6/squared/releases/tag/v0.4.13-ruby
740
+ [0.4.12]: https://github.com/anpham6/squared/releases/tag/v0.4.12-ruby
668
741
  [0.4.11]: https://github.com/anpham6/squared/releases/tag/v0.4.11-ruby
669
742
  [0.4.10]: https://github.com/anpham6/squared/releases/tag/v0.4.10-ruby
670
743
  [0.4.9]: https://github.com/anpham6/squared/releases/tag/v0.4.9-ruby
@@ -677,6 +750,7 @@
677
750
  [0.4.2]: https://github.com/anpham6/squared/releases/tag/v0.4.2-ruby
678
751
  [0.4.1]: https://github.com/anpham6/squared/releases/tag/v0.4.1-ruby
679
752
  [0.4.0]: https://github.com/anpham6/squared/releases/tag/v0.4.0-ruby
753
+ [0.3.11]: https://github.com/anpham6/squared/releases/tag/v0.3.11-ruby
680
754
  [0.3.10]: https://github.com/anpham6/squared/releases/tag/v0.3.10-ruby
681
755
  [0.3.9]: https://github.com/anpham6/squared/releases/tag/v0.3.9-ruby
682
756
  [0.3.8]: https://github.com/anpham6/squared/releases/tag/v0.3.8-ruby
@@ -688,6 +762,7 @@
688
762
  [0.3.2]: https://github.com/anpham6/squared/releases/tag/v0.3.2-ruby
689
763
  [0.3.1]: https://github.com/anpham6/squared/releases/tag/v0.3.1-ruby
690
764
  [0.3.0]: https://github.com/anpham6/squared/releases/tag/v0.3.0-ruby
765
+ [0.2.11]: https://github.com/anpham6/squared/releases/tag/v0.2.11-ruby
691
766
  [0.2.10]: https://github.com/anpham6/squared/releases/tag/v0.2.10-ruby
692
767
  [0.2.9]: https://github.com/anpham6/squared/releases/tag/v0.2.9-ruby
693
768
  [0.2.8]: https://github.com/anpham6/squared/releases/tag/v0.2.8-ruby
@@ -699,6 +774,7 @@
699
774
  [0.2.2]: https://github.com/anpham6/squared/releases/tag/v0.2.2-ruby
700
775
  [0.2.1]: https://github.com/anpham6/squared/releases/tag/v0.2.1-ruby
701
776
  [0.2.0]: https://github.com/anpham6/squared/releases/tag/v0.2.0-ruby
777
+ [0.1.8]: https://github.com/anpham6/squared/releases/tag/v0.1.8-ruby
702
778
  [0.1.7]: https://github.com/anpham6/squared/releases/tag/v0.1.7-ruby
703
779
  [0.1.6]: https://github.com/anpham6/squared/releases/tag/v0.1.6-ruby
704
780
  [0.1.5]: https://github.com/anpham6/squared/releases/tag/v0.1.5-ruby
data/README.md CHANGED
@@ -140,13 +140,13 @@ rake clone # node + docs
140
140
  # PIPE_FAIL={0,1}
141
141
  # PORT=3000
142
142
  docker build -t squared --build-arg MANIFEST=prod --build-arg NODE_ENV=production .
143
- docker build -t node --build-arg NODE_TAG=22 --build-arg NODE_INSTALL=pnpm -f Dockerfile.slim .
143
+ docker build -t node --build-arg NODE_TAG=22 --build-arg NODE_INSTALL=pnpm -f slim.Dockerfile .
144
144
  NODE=22 docker buildx bake node
145
145
  # OR
146
- docker build -t ruby --build-arg RUBY_TAG=3.4.0 --build-arg NODE_VERSION=22 --build-arg PIPE_FAIL=0 -f Dockerfile.ruby .
146
+ docker build -t ruby --build-arg RUBY_TAG=3.4.0 --build-arg NODE_VERSION=22 --build-arg PIPE_FAIL=0 -f ruby.Dockerfile .
147
147
  RUBY=3.4.0 docker buildx bake ruby
148
148
  # OR
149
- docker build -t nginx --build-arg NGINX_VERSION=1.27 --build-arg PORT=3000 --build-arg NODE_VERSION=20 -f Dockerfile.nginx .
149
+ docker build -t nginx --build-arg NGINX_VERSION=1.27 --build-arg PORT=3000 --build-arg NODE_VERSION=20 -f nginx.Dockerfile .
150
150
  NGINX=1.27 docker buildx bake nginx
151
151
 
152
152
  # Express
data/README.ruby.md CHANGED
@@ -234,17 +234,74 @@ Workspace::Application
234
234
  .build(parallel: ["clone"]) # rake clone + rake clone:sync
235
235
  ```
236
236
 
237
- ### Graph
237
+ ### Build
238
+
239
+ #### Chain
240
+
241
+ There has to be at least one project which uses the ``step`` attribute. Other placement attributes are ignored.
242
+
243
+ **NOTE**: Projects can only reference non-global namespaced tasks. (e.g. with ":")
244
+
245
+ ```ruby
246
+ Workspace::Application
247
+ .new
248
+ .with(:node) do
249
+ add("e-mc", "emc") do
250
+ chain "all", "clean", step: 1 # Required
251
+ chain "all", "build", step: 2
252
+ end
253
+ add("pi-r", "pir") do
254
+ chain "all", "build", after: "emc:build" # step: 3
255
+ end
256
+ add("pi-r2", "pir2") do
257
+ chain "all", "build", before: "squared" # step: 3
258
+ end
259
+ add("squared-express", "express") do
260
+ chain "all", "clean", with: "emc" # step: 1
261
+ chain "all", "build", with: "pir" # step: 3
262
+ end
263
+ add("squared") do
264
+ revbuild(include: %w[src/ framework/ types/]) # Git revision build command (optional)
265
+ chain "all", "revbuild", after: "express:build" # step: 4
266
+ chain "publish", "bump:patch", "publish:latest", step: 0, sync: true # rake publish -> squared:bump:patch -> squared:publish:latest
267
+ end
268
+ end
269
+ .with(:python) do
270
+ doc("make html")
271
+ add("android-docs") do
272
+ chain "all", "doc", with: "squared", after: "squared" # step: 4
273
+ end
274
+ add("chrome-docs") do
275
+ chain "all", "doc", with: "squared", before: "squared:revbuild" # Same
276
+ end
277
+ end
278
+ .chain "all", "status", with: "squared", after: "android-docs" # Global tasks (e.g. without ":")
279
+ .build
280
+ ```
281
+
282
+ ```sh
283
+ rake all # all[1-3-4]
284
+ rake all:print
285
+ ```
286
+
287
+ Threaded is the default when there are two or more tasks. Using ``with`` and either **before** or **after** will create a synchronous group.
288
+
289
+ * Step 1: emc:clean + express:clean (thread)
290
+ * Step 2: emc:build (sync)
291
+ * Step 3: pir:build + express:build + pir2:build (thread)
292
+ * Step 4: chrome-docs:doc + squared:revbuild + android-docs:doc + status (sync)
293
+
294
+ #### Graph
238
295
 
239
296
  ```ruby
240
297
  Workspace::Application
241
298
  .new(main: "squared")
242
- .graph(["depend"], ref: :git) # Optional
299
+ .graph(["depend"], ref: :git) # Optional
243
300
  .with(:python) do
244
301
  doc(windows? ? ".\make.bat html" : "make html") # rake android-docs:doc | rake doc:python
245
302
  add("android-docs", "android", venv: "/home/user/.venv") # rake android-docs:depend
246
- add("chrome-docs", "chrome", graph: "android", venv: ".venv") do # /workspaces/chrome-docs/.venv
247
- variable_set :dependindex, 2 # Use Poetry for dependencies (optional)
303
+ add("chrome-docs", "chrome", graph: "android", venv: %w[.venv --clear]) do # /workspaces/chrome-docs/.venv
304
+ variable_set :dependindex, 1 # Use Poetry for dependencies (optional)
248
305
  end
249
306
  end
250
307
  .with(:node) do
@@ -344,18 +401,14 @@ rake squared:graph:run[express,pir] # emc + pir + express + squared
344
401
  rake squared:graph:run[node,-emc] # pir + express + squared
345
402
  ```
346
403
 
347
- ### Batch
404
+ ### Tasks
348
405
 
349
406
  ```ruby
350
407
  Workspace::Series.batch(:ruby, :node, {
351
408
  stage: %i[graph test],
352
409
  reset: %i[stash pull]
353
410
  })
354
- ```
355
411
 
356
- ### Rename
357
-
358
- ```ruby
359
412
  Workspace::Series.rename("depend", "install")
360
413
  ```
361
414
 
@@ -423,9 +476,11 @@ Non-task:
423
476
  * active
424
477
  * inline
425
478
  * subject
479
+ * border
426
480
  * warn
427
481
  * caution
428
482
  * current
483
+ * latest
429
484
  * extra
430
485
  * major
431
486
  * red
@@ -541,6 +596,9 @@ BANNER_${NAME}=0 #
541
596
 
542
597
  REVBUILD_FORCE=1 # Rebuild all targets
543
598
  REVBUILD_FORCE_${NAME}=1 # Rebuild project
599
+
600
+ PREREQS_${NAME}=build,copy # Class method name to invoke
601
+ PREREQS_${REF}=depend # e.g. Node
544
602
  ```
545
603
 
546
604
  ### Graph
@@ -561,7 +619,6 @@ LOG_AUTO # year,y,month,m,day,d,1
561
619
  # Optional
562
620
  LOG_DIR # exist?
563
621
  LOG_LEVEL # See gem "logger"
564
- LOG_COLUMNS # terminal width (default: 80)
565
622
  ```
566
623
 
567
624
  ### Git
@@ -584,7 +641,7 @@ GIT_AUTOSTASH_${NAME}=0 # rebase (project only)
584
641
  | checkout | track | COUNT=n |
585
642
  | checkout | global path | HEAD=s PATHSPEC=s |
586
643
  | checkout | * | FORCE MERGE |
587
- | clone | * | DEPTH=n ORIGIN=s BRANCH=s LOCAL=0,1 |
644
+ | clone | * | DEPTH=n ORIGIN=s BRANCH=s REVISION=s LOCAL=0,1 |
588
645
  | commit | * | UPSTREAM=s DRY_RUN EDIT=0 M|MESSAGE=s |
589
646
  | diff | -between -contain | MERGE_BASE |
590
647
  | diff | head branch | INDEX=n |
@@ -46,6 +46,7 @@ module Squared
46
46
  active: [:bold],
47
47
  inline: [:bold],
48
48
  subject: [:bold],
49
+ border: nil,
49
50
  warn: %i[white red!],
50
51
  caution: %i[black yellow!],
51
52
  current: nil,
@@ -167,7 +167,7 @@ module Squared
167
167
  color ? sub_style(ret, *styles) : ret
168
168
  end
169
169
 
170
- def log_message(level, *args, subject: nil, hint: nil, color: ARG[:COLOR], append: true, pass: false)
170
+ def log_message(level, *args, subject: nil, hint: nil, append: true, pass: false, color: ARG[:COLOR])
171
171
  args = args.map(&:to_s)
172
172
  if level.is_a?(::Numeric)
173
173
  if append && respond_to?(:log)
@@ -176,10 +176,10 @@ module Squared
176
176
  end
177
177
  return false if !pass && level < ARG[:LEVEL]
178
178
  end
179
- if args.size > 1 && !hint
179
+ if (args.size > 1 && !hint) || hint == false
180
180
  title = log_title(level, color: false)
181
- sub = { pat: /\A(#{Regexp.escape(title)})(.*)\z/m, styles: __get__(:theme)[:logger][log_sym(level)] } if color
182
- emphasize(args, title: title + (subject ? " #{subject}" : ''), sub: sub)
181
+ sub = [pat: /\A(#{Regexp.escape(title)})(.*)\z/m, styles: __get__(:theme)[:logger][log_sym(level)]] if color
182
+ emphasize(args, title: title + (subject ? " #{subject}" : ''), sub: sub, pipe: -1)
183
183
  else
184
184
  msg = [log_title(level, color: color)]
185
185
  msg << (color ? sub_style(subject, styles: (@theme && @theme[:subject]) || :bold) : subject) if subject
@@ -211,7 +211,8 @@ module Squared
211
211
  (empty ? args.reject { |val| val.nil? || val.empty? } : args).join(space) + (hint ? " (#{hint})" : '')
212
212
  end
213
213
 
214
- def emphasize(val, title: nil, footer: nil, right: false, cols: nil, sub: nil, border: nil, pipe: nil)
214
+ def emphasize(val, title: nil, footer: nil, right: false, cols: nil, sub: nil, pipe: nil,
215
+ border: @theme && @theme[:border])
215
216
  n = 0
216
217
  max = ->(v) { n = [n, v.max_by(&:size).size].max }
217
218
  set = lambda do |v|
@@ -271,6 +272,8 @@ module Squared
271
272
  yield out
272
273
  elsif pipe
273
274
  case pipe
275
+ when -1
276
+ return out
274
277
  when 0
275
278
  pipe = $stdin
276
279
  when 2
@@ -9,23 +9,23 @@ module Squared
9
9
  module_function
10
10
 
11
11
  def shell_escape(val, quote: false, force: false, double: false, option: false, override: false)
12
- if (r = /\A(--?)([^= ]+)((=|\s+)(["'])?(.+?)(["'])?)?\z/m.match(val = val.to_s))
13
- return val if !r[3] || (!r[5] && !r[6].match?(/\s/))
14
-
15
- combine = lambda do |opt|
16
- if r[2] =~ /\A(["'])(.+)\1\z/m
17
- double = $1 == '"'
18
- r[2] = $2
19
- override = true
20
- end
21
- r[1] + r[2] + r[4] + shell_quote(opt, double: double, force: force, override: override)
12
+ if (r = /\A(--?)([^= ]+)((=|\s+)(["'])?(?(5)(.*)\5|(.*)))?\z/m.match(val = val.to_s))
13
+ if (data = r[2].match(/\A(["'])(.+)\1\z/))
14
+ double = data[1] == '"'
15
+ override = true
16
+ elsif !r[3] || r[6]
17
+ return val
22
18
  end
23
- if r[5] == r[7]
24
- r[5] ? val : combine.call(r[6])
19
+ if r[7].match?(/\A["']/)
20
+ opt = "#{r[7]}#{r[7][0]}"
21
+ elsif r[7].match?(/["']\z/)
22
+ opt = "#{r[7][-1]}#{r[7]}"
25
23
  else
26
- force = true
27
- combine.call(r[5] + r[6] + r[7])
24
+ return val unless r[7].match?(/\s/)
25
+
26
+ opt = r[7]
28
27
  end
28
+ r[1] + (data ? data[2] : r[2]) + r[4] + shell_quote(opt, double: double, force: force, override: override)
29
29
  elsif option && val =~ /\A([^=]+)=(.+)\z/m
30
30
  return val if $2.match?(/\A(["']).+\1\z/m)
31
31
 
@@ -11,12 +11,12 @@ module Squared
11
11
  def shell(*args, **kwargs)
12
12
  if RUBY_VERSION < '2.6'
13
13
  exception = kwargs.delete(:exception)
14
- ret = system(*args, **kwargs)
14
+ ret = Kernel.system(*args, **kwargs)
15
15
  return ret if ret || !exception
16
16
 
17
17
  raise $?.to_s
18
18
  else
19
- system(*args, **kwargs)
19
+ Kernel.system(*args, **kwargs)
20
20
  end
21
21
  end
22
22
 
@@ -47,9 +47,7 @@ module Squared
47
47
 
48
48
  def task_invoked?(*args)
49
49
  Rake::Task.tasks.any? do |obj|
50
- next unless obj.already_invoked
51
-
52
- args.any? { |val| val.is_a?(::Regexp) ? obj.name.match?(val) : val == obj.name }
50
+ obj.already_invoked && args.any? { |val| val.is_a?(::Regexp) ? obj.name.match?(val) : val == obj.name }
53
51
  end
54
52
  end
55
53
 
@@ -157,15 +155,13 @@ module Squared
157
155
  when ::Numeric
158
156
  return key if key.between?(0, 2)
159
157
  end
158
+ return default unless default.is_a?(::String)
159
+
160
160
  begin
161
- if default.is_a?(::String)
162
- default = (root ? Pathname.new(root) + default : Pathname.new(default)).realdirpath
163
- end
161
+ (root ? Pathname.new(root) + default : Pathname.new(default)).realdirpath
164
162
  rescue StandardError => e
165
163
  warn e
166
164
  1
167
- else
168
- default
169
165
  end
170
166
  end
171
167
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.4.11'
4
+ VERSION = '0.4.13'
5
5
  end
@@ -106,6 +106,7 @@ module Squared
106
106
  else
107
107
  @theme = {}
108
108
  end
109
+ @chain = {}
109
110
  @script = {
110
111
  group: {},
111
112
  ref: {},
@@ -132,8 +133,9 @@ module Squared
132
133
  def initialize_session
133
134
  return unless @pipe.is_a?(Pathname)
134
135
 
135
- bord = '#' * Project.line_width
136
- puts bord, format('Session started on %s by %s', Time.now.to_s, @main), bord
136
+ msg = "Session started on #{Time.now} by #{@main}"
137
+ bord = '#' * s.size
138
+ puts bord, msg, bord
137
139
  end
138
140
 
139
141
  def build(parallel: [], pass: nil, **kwargs)
@@ -165,6 +167,8 @@ module Squared
165
167
  __build__(**kwargs)
166
168
 
167
169
  yield self if block_given?
170
+
171
+ __chain__(**kwargs)
168
172
  @closed = true
169
173
  self
170
174
  end
@@ -197,6 +201,34 @@ module Squared
197
201
  script_command :run, script, group, ref, on, &blk
198
202
  end
199
203
 
204
+ def chain(task, *action, project: nil, step: 0, with: nil, before: nil, after: nil, sync: false,
205
+ group: @group, ref: @ref)
206
+ keys = if project
207
+ action.map! { |val| task_join(project.name, val) }
208
+ nil
209
+ elsif (target = group || ref)
210
+ action.map! { |val| task_name(task_join(val, target)) }
211
+ nil
212
+ else
213
+ action.map! { |val| task_name(val) }
214
+ prefix ? nil : @project.keys
215
+ end
216
+ ns = lambda do |val|
217
+ next if (ret = as_a(val, :to_s, flat: true)).empty?
218
+
219
+ ret.map! do |arg|
220
+ if arg.include?(':') || (keys && !keys.include?(arg))
221
+ task_name(arg)
222
+ else
223
+ /\A#{Regexp.escape(task_name(arg))}:/
224
+ end
225
+ end
226
+ end
227
+ data = Support::ChainData.new(action, step, ns.call(with), ns.call(before), ns.call(after), sync)
228
+ (@chain[task_name(task.to_s)] ||= []) << data
229
+ self
230
+ end
231
+
200
232
  def script(script, group: @group, ref: @ref, on: nil)
201
233
  script_command :script, script, group, ref, on
202
234
  end
@@ -305,13 +337,14 @@ module Squared
305
337
  index = 0
306
338
  while @project[name]
307
339
  index += 1
308
- name = "#{project}-#{index}"
340
+ name = task_name "#{project}-#{index}"
309
341
  end
310
342
  proj = ((if !ref.is_a?(Class)
311
343
  Application.find(ref, path: path)
312
344
  elsif ref < Project::Base
313
345
  ref
314
346
  end) || @kind[name]&.last || Project::Base).new(self, path, name, **kwargs)
347
+ proj.__send__(:index_set, @project.size)
315
348
  @project[name] = proj
316
349
  __get__(:project)[name] = proj unless kwargs[:private]
317
350
  proj.instance_eval(&blk) if block_given?
@@ -506,7 +539,7 @@ module Squared
506
539
  def format_desc(val, opts = nil, arg: 'opts*', before: nil, after: nil, out: false)
507
540
  return unless TASK_METADATA || out
508
541
 
509
- val = val.to_s.split(':') if val.is_a?(String)
542
+ val = val.split(':') if val.is_a?(String)
510
543
  if before || after || opts
511
544
  pos = []
512
545
  pos << (before.is_a?(Array) ? before.join(',') : before) if before
@@ -651,13 +684,129 @@ module Squared
651
684
  task Rake.application.default_task_name => out
652
685
  end
653
686
 
687
+ def __chain__(*)
688
+ @chain.each do |key, group|
689
+ level = []
690
+ sync = []
691
+ failed = []
692
+ i = 0
693
+ pass = nil
694
+ until (i > 0 && !group.compact! && !pass) || group.empty?
695
+ group.each_with_index do |data, j|
696
+ if i == 0
697
+ action, reject = data.action.partition { |val| task_defined?(val) }
698
+ failed.concat(reject)
699
+ next group[j] = nil if action.empty?
700
+
701
+ step = data.step
702
+ data.action = action
703
+ else
704
+ step = 0
705
+ catch :found do
706
+ has = ->(c, d) { c.any? { |e| e.is_a?(Regexp) ? d.match?(e) : d == e } }
707
+ w = data.with
708
+ a = data.after
709
+ b = data.before
710
+ level.each_with_index do |tasks, k|
711
+ with = lambda do |n|
712
+ tasks.insert(n, *data.action)
713
+ sync << tasks
714
+ data.action.clear
715
+ end
716
+ tasks&.each_with_index do |v1, l|
717
+ index = k if w && has.call(w, v1)
718
+ if a && has.call(a, v1)
719
+ if index
720
+ with.call(l + 1)
721
+ throw :found
722
+ else
723
+ index = k + 1
724
+ end
725
+ elsif b && has.call(b, v1)
726
+ if index
727
+ with.call(l)
728
+ throw :found
729
+ else
730
+ index = k - 1
731
+ end
732
+ elsif index
733
+ if a || b
734
+ tasks.each_with_index do |v2, m|
735
+ if a && has.call(a, v2)
736
+ with.call(m + 1)
737
+ throw :found
738
+ elsif b && has.call(b, v2)
739
+ with.call(m)
740
+ throw :found
741
+ end
742
+ end
743
+ if !pass
744
+ pass = [i, data]
745
+ elsif pass.include?(data)
746
+ if i == pass.first + 1
747
+ pass.delete(data)
748
+ pass = nil if pass.size == 1
749
+ end
750
+ else
751
+ pass << data
752
+ end
753
+ next
754
+ end
755
+ else
756
+ next
757
+ end
758
+ step = index == -1 ? -1 : index + 1
759
+ throw :found
760
+ end
761
+ end
762
+ end
763
+ end
764
+ if step == -1
765
+ level.unshift(data.action)
766
+ step = 0
767
+ elsif step > 0
768
+ (level[step -= 1] ||= []).concat(data.action)
769
+ elsif !data.action.empty?
770
+ next
771
+ end
772
+ sync << level[step] if data.sync
773
+ group[j] = nil
774
+ pass = nil
775
+ end
776
+ i += 1
777
+ end
778
+ level.compact!
779
+ sync.uniq!
780
+ series.chain(key, level, sync: sync)
781
+ next if task_defined?(key = task_join(key, 'print'))
782
+
783
+ format_desc key
784
+ task key do
785
+ unless failed.empty? && group.empty?
786
+ puts log_message(Logger::ERROR, *(failed + group.map { |val| val.action }.flatten),
787
+ subject: 'failed placement', hint: false, pass: true)
788
+ end
789
+ cols = level.flatten(1).map(&:size).max
790
+ level.each_with_index do |grp, n|
791
+ title = "Step #{n.succ}#{if !sync.include?(grp) || (grp.size == 1 && series.parallel.include?(grp.first))
792
+ ''
793
+ else
794
+ ' (sync)'
795
+ end}"
796
+ emphasize(grp, title: title, cols: [cols, title.size].max, border: theme[:border],
797
+ sub: [pat: /\A(Step \d+)(.*)\z/, styles: theme[:header]])
798
+ end
799
+ end
800
+ end
801
+ end
802
+
654
803
  def puts(*args)
655
804
  puts_oe(*args, pipe: pipe)
656
805
  end
657
806
 
658
807
  def script_command(task, val, group, ref, on, &blk)
659
808
  if block_given?
660
- val = Struct.new(:run, :block).new(val, blk)
809
+ val = Support::RunData.new(val, blk)
661
810
  elsif !val
662
811
  return self
663
812
  end