squared 0.4.7 → 0.4.8

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: 0cf1642a52eeb080a0aa4dadb9c7fb5a3060612904bd06a8161ab18b4e333a9a
4
- data.tar.gz: 7bad82be155782b37036cc2d447395a37585ed33b4d37c5281e3ccd3f902ea33
3
+ metadata.gz: 483002a469a23461dcee07ded3610011d831f2c737142cd4b464899fbdf70cde
4
+ data.tar.gz: 4601b03b61f6270e8b3841777effd420b47c752ddbe96924ca329ee92c98ee4c
5
5
  SHA512:
6
- metadata.gz: 77fda3077ec4c929fad3003d7255d5aeb7b0f7fa2d14a717678772572a3a972214247aabc6911d8e717103b855ae1109c81c118ca5564f46fd4e79ecfc62cf6a
7
- data.tar.gz: 29e84ae67dc2aad4ac61c99407443d65210f96af60649784c9264a75c686473b919328a9ea6d97b6bda6e4fd61aeaa97df4e196bdf87f6e32819dfea1306c76a
6
+ metadata.gz: 5767519e7f0150a753c0cbf97aa59292ad59a29b4f78c6e7596e5bd369b035258339d062b896c59d37496ccdcd4224179a745065f1dc41a35f876a507f30e171
7
+ data.tar.gz: c457cbd60b4865aea1e218ce9609c4094f618cc4b404eac309c8235b3bc116599487d1c641151cd99bceab6d28a03b48abd1774e447017afb2d28439aca40298
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.8] - 2025-04-21
4
+
5
+ ### Added
6
+
7
+ - Workspace project tasks can be executed with a block.
8
+ - Gem command outdated supports semantic version upgrades.
9
+ - Project task unpack supports "gem" extension.
10
+ - Ruby command version was implemented.
11
+ - Ruby command file and script were implemented.
12
+ - Git global command autostash was created.
13
+ - Workspace static method exclude for global tasks was created.
14
+ - Git command checkout action branch and track are interactive.
15
+ - Git command branch action set and delete are interactive.
16
+ - Git command tag action delete is interactive.
17
+ - Docker command image run and container exec are interactive.
18
+ - Docker command network connect and disconnect are interactive.
19
+
20
+ ### Changed
21
+
22
+ - Project methods with conflicting names were given "!" suffix.
23
+ - Docker exec args are run without escape protection.
24
+ - Format method enable_aixterm no longer accepts a block.
25
+
26
+ ### Fixed
27
+
28
+ - Git command log action view did not parse options.
29
+ - Project task unpack did not use custom extensions.
30
+ - Docker did not append secrets when given a string.
31
+
3
32
  ## [0.4.7] - 2025-04-17
4
33
 
5
34
  ### Added
@@ -533,6 +562,7 @@
533
562
 
534
563
  - Changelog was created.
535
564
 
565
+ [0.4.8]: https://github.com/anpham6/squared/releases/tag/v0.4.8-ruby
536
566
  [0.4.7]: https://github.com/anpham6/squared/releases/tag/v0.4.7-ruby
537
567
  [0.4.6]: https://github.com/anpham6/squared/releases/tag/v0.4.6-ruby
538
568
  [0.4.5]: https://github.com/anpham6/squared/releases/tag/v0.4.5-ruby
data/README.ruby.md CHANGED
@@ -278,6 +278,10 @@ Workspace::Application
278
278
  .with(:ruby) do
279
279
  run("gem build") # gem build
280
280
  # OR
281
+ run("gem build", on: { first: -> { p "2" }, last: -> { p "4" } }) do # run | depend | graph | clean | doc | lint | test
282
+ p "1"
283
+ end
284
+ # OR
281
285
  run(["gem build", "--force", { "RUBY_VERSION" => "3.4.0" }]) # RUBY_VERSION="3.4.0" gem build --force
282
286
  # OR
283
287
  run({ #
@@ -448,6 +452,8 @@ Most project classes will inherit from `Git` which enables these tasks:
448
452
  You can disable all of them at once using the `exclude` property.
449
453
 
450
454
  ```ruby
455
+ Workspace::Application.exclude('autostash', 'rebase')
456
+
451
457
  Workspace::Application
452
458
  .new
453
459
  .add("squared", exclude: :git)
@@ -462,6 +468,15 @@ Workspace::Application
462
468
  .pass("pull", ref: :node) { read_packagemanager(:private) }
463
469
  ```
464
470
 
471
+ ### Commit Hash
472
+
473
+ Commands which use commit hashes are parsed using string interpolation format as to not be confused for an option.
474
+
475
+ ```sh
476
+ rake squared:log:view[#{af012345}] # git log af012345
477
+ rake squared:log:view[H1,H5,all,lib,./H12345] # git log --all HEAD~1 HEAD~5 -- 'lib' 'H12345'
478
+ ```
479
+
465
480
  ## Environment
466
481
 
467
482
  ### Path
@@ -592,6 +607,7 @@ DOCKER_OPTIONS=q,no-cache # all
592
607
  DOCKER_OPTIONS_${NAME}=v,no-cache=false # project only (override)
593
608
  DOCKER_TAG=latest # all
594
609
  DOCKER_TAG_${NAME}=v0.1.0 # project only (override)
610
+ DOCKER_ALL=1 # list every image/container
595
611
  ```
596
612
 
597
613
  | Command | Flag | ENV |
@@ -11,6 +11,7 @@ module Squared
11
11
  COMMON: true,
12
12
  VERBOSE: nil,
13
13
  BANNER: true,
14
+ CHOICE: 25,
14
15
  QUOTE: "'",
15
16
  SPACE: ' => ',
16
17
  GRAPH: ['|', '-', '|', '\\', '-'].freeze,
@@ -12,7 +12,7 @@ module Squared
12
12
  super[/[^:]+\z/, 0]
13
13
  end
14
14
 
15
- def_delegators :@data, :+, :each, :each_with_index, :entries, :merge, :to_a, :include?
15
+ def_delegators :@data, :+, :each, :each_with_index, :entries, :merge, :include?
16
16
 
17
17
  def initialize(data = [])
18
18
  @data = Set.new(data)
@@ -22,6 +22,10 @@ module Squared
22
22
  @data.add(val.to_sym)
23
23
  end
24
24
 
25
+ def to_a
26
+ @data.to_a.freeze
27
+ end
28
+
25
29
  def to_s
26
30
  @data.to_s.sub('Set', SymSet.to_s)
27
31
  end
@@ -71,6 +75,20 @@ module Squared
71
75
  block_given? ? ret.reject(&blk) : ret
72
76
  end
73
77
 
78
+ def and(*args)
79
+ self << '&&'
80
+ merge(args)
81
+ end
82
+
83
+ def or(*args)
84
+ self << '||'
85
+ merge(args)
86
+ end
87
+
88
+ def with(*args, &blk)
89
+ temp('&&', *args, &blk)
90
+ end
91
+
74
92
  def temp(*args, &blk)
75
93
  args.compact!
76
94
  ret = pass(&blk)
@@ -28,18 +28,22 @@ module Squared
28
28
  bright_cyan!: '106',
29
29
  bright_white!: '107'
30
30
  }.freeze
31
- AIX_GRAPH = ['│', '─', '├', '└', '┬'].freeze
32
- AIX_BORDER = ['│', '─', '╭', '╮', '╯', '╰', '├', '┤', '┬', '┴'].freeze
31
+ BOX_GRAPH = ['│', '─', '├', '└', '┬'].freeze
32
+ BOX_BORDER = ['│', '─', '╭', '╮', '╯', '╰', '├', '┤', '┬', '┴'].freeze
33
33
  TEXT_STYLE = [:bold, :dim, :italic, :underline, :blinking, nil, :inverse, :hidden, :strikethrough].freeze
34
- private_constant :AIX_TERM, :AIX_GRAPH, :AIX_BORDER, :TEXT_STYLE
34
+ private_constant :AIX_TERM, :BOX_GRAPH, :BOX_BORDER, :TEXT_STYLE
35
35
 
36
36
  def enable_aixterm
37
37
  unless (colors = __get__(:colors)).frozen?
38
38
  colors.merge!(AIX_TERM)
39
- ARG[:GRAPH] = AIX_GRAPH
40
- ARG[:BORDER] = AIX_BORDER
41
39
  end
42
- block_given? ? yield(self) : self
40
+ self
41
+ end
42
+
43
+ def enable_drawing
44
+ ARG[:GRAPH] = BOX_GRAPH
45
+ ARG[:BORDER] = BOX_BORDER
46
+ self
43
47
  end
44
48
 
45
49
  private
@@ -68,16 +72,16 @@ module Squared
68
72
  end
69
73
  if type.is_a?(::Numeric)
70
74
  f, b = type.to_s.split('.')
71
- s = wrap.(s, ['38', '5', f]) if f[0] != '-' && f.to_i <= 255
75
+ s = wrap.call(s, ['38', '5', f]) if f[0] != '-' && f.to_i <= 255
72
76
  if b
73
77
  b = b[0, 3]
74
- s = wrap.(s, ['48', '5', b]) unless b.to_i > 255
78
+ s = wrap.call(s, ['48', '5', b]) unless b.to_i > 255
75
79
  end
76
80
  else
77
81
  t = type.to_sym
78
82
  if (c = __get__(:colors)[t])
79
83
  if index == -1
80
- s = wrap.(s, [c])
84
+ s = wrap.call(s, [c])
81
85
  else
82
86
  code << c
83
87
  end
@@ -95,7 +99,7 @@ module Squared
95
99
  end
96
100
  return ret.join if index == -1
97
101
 
98
- ret = wrap.(ret, code) unless code.empty?
102
+ ret = wrap.call(ret, code) unless code.empty?
99
103
  return ret unless data
100
104
 
101
105
  out = ''.dup
@@ -133,9 +137,9 @@ module Squared
133
137
 
134
138
  set = ->(k, v) { data[k] = check_style(v, empty: empty) }
135
139
  if key.is_a?(::Hash)
136
- key.each { |k, v| set.(k, v || args) }
140
+ key.each { |k, v| set.call(k, v || args) }
137
141
  else
138
- set.(key.to_sym, args)
142
+ set.call(key.to_sym, args)
139
143
  end
140
144
  end
141
145
 
@@ -219,18 +223,18 @@ module Squared
219
223
  max = ->(v) { n = [n, v.max_by(&:size).size].max }
220
224
  set = lambda do |v|
221
225
  ret = as_a(v, :to_s)
222
- max.(ret)
226
+ max.call(ret)
223
227
  ret
224
228
  end
225
- title &&= set.(title)
226
- footer &&= set.(footer)
229
+ title &&= set.call(title)
230
+ footer &&= set.call(footer)
227
231
  if val.is_a?(::Array)
228
232
  lines = val.map(&:to_s)
229
233
  else
230
234
  lines = val.to_s.lines(chomp: true)
231
235
  lines[0] = "#{val.class}: #{lines.first}" if (err = val.is_a?(StandardError))
232
236
  end
233
- n = cols || max.(lines)
237
+ n = cols || max.call(lines)
234
238
  if $stdout.tty?
235
239
  require 'io/console'
236
240
  (n = [n, $stdout.winsize[1] - 4].min) rescue nil
@@ -253,13 +257,13 @@ module Squared
253
257
  end
254
258
  s
255
259
  end
256
- out << draw.(b2, b3)
260
+ out << draw.call(b2, b3)
257
261
  if title
258
- out.concat(title.map { |t| pr.(t) })
259
- out << draw.(b6, b7)
262
+ out.concat(title.map { |t| pr.call(t) })
263
+ out << draw.call(b6, b7)
260
264
  end
261
- lines.each { |line| out << pr.(line) }
262
- out << draw.(b5, b4)
265
+ lines.each { |line| out << pr.call(line) }
266
+ out << draw.call(b5, b4)
263
267
  if footer
264
268
  unless sub.empty? && !right
265
269
  footer.map! do |s|
@@ -21,7 +21,7 @@ module Squared
21
21
  return false
22
22
  end
23
23
  attempts -= 1
24
- exit 1 unless attempts >= 0
24
+ exit 1 unless attempts > 0
25
25
  end
26
26
  rescue Interrupt
27
27
  puts
@@ -31,6 +31,44 @@ module Squared
31
31
  end
32
32
  end
33
33
  end
34
+
35
+ def choice(msg, list = nil, min: 1, max: 1, multiple: false, attempts: 5, timeout: 60)
36
+ require 'readline'
37
+ require 'timeout'
38
+ if list
39
+ items = []
40
+ list.each_with_index do |val, index|
41
+ puts "#{index.succ.to_s.rjust(2)}. #{val}"
42
+ items << val.chomp
43
+ end
44
+ max = items.size
45
+ msg = "#{msg}: [1-#{max}#{multiple ? '|,' : ''}] "
46
+ end
47
+ return unless max >= min
48
+
49
+ valid = ->(s) { s.match?(/^-?\d+$/) && s.to_i.between?(min, max) }
50
+ Timeout.timeout(timeout) do
51
+ begin
52
+ while (ch = Readline.readline(msg, true))
53
+ ch = ch.strip
54
+ if multiple
55
+ a = ch.split(/\s*,\s*/)
56
+ b = a.select { |s| valid.call(s) }.map!(&:to_i)
57
+ return items ? b.map! { |i| items[i - 1] } : b if a.size == b.size
58
+ elsif valid.call(ch)
59
+ return items ? items[ch.to_i - 1] : ch.to_i
60
+ end
61
+ attempts -= 1
62
+ exit 1 unless attempts > 0
63
+ end
64
+ rescue Interrupt
65
+ puts
66
+ exit 0
67
+ else
68
+ multiple ? [] : nil
69
+ end
70
+ end
71
+ end
34
72
  end
35
73
  end
36
74
  end
@@ -21,11 +21,13 @@ module Squared
21
21
  r[1] + r[2] + r[4] + shell_quote(opt, double: double, force: force, override: override)
22
22
  end
23
23
  if r[5] == r[7]
24
- r[5] ? val : combine.(r[6])
24
+ r[5] ? val : combine.call(r[6])
25
25
  else
26
26
  force = true
27
- combine.(r[5] + r[6] + r[7])
27
+ combine.call(r[5] + r[6] + r[7])
28
28
  end
29
+ elsif val.empty?
30
+ ''
29
31
  elsif Rake::Win32.windows?
30
32
  quote ? shell_quote(val, double: double, force: force) : val
31
33
  else
@@ -35,7 +37,7 @@ module Squared
35
37
 
36
38
  def shell_quote(val, option: true, force: true, double: false, override: false)
37
39
  val = val.to_s
38
- return val if !force && !val.include?(' ')
40
+ return val if val.empty? || (!force && !val.include?(' '))
39
41
  return val if option && val.match?(/(?:\A|\S=|[^=]\s+|#{Rake::Win32.windows? ? '[\\\/]' : '\/'})(["']).+\1\z/m)
40
42
 
41
43
  if double || Rake::Win32.windows? || (ARG[:QUOTE] == '"' && !override)
@@ -45,8 +47,8 @@ module Squared
45
47
  end
46
48
  end
47
49
 
48
- def shell_option(flag, val = nil, escape: true, quote: true, force: true, double: false, override: false,
49
- merge: false)
50
+ def shell_option(flag, val = nil, escape: true, quote: true, option: true, force: true, double: false,
51
+ merge: false, override: false)
50
52
  flag = flag.to_s
51
53
  if flag =~ /\A(["'])(.+)\1\z/
52
54
  double = $1 == '"'
@@ -66,8 +68,10 @@ module Squared
66
68
  "#{a}#{flag}#{if val
67
69
  "#{b}#{if escape
68
70
  shell_escape(val, quote: quote, double: double, override: override)
71
+ elsif quote
72
+ shell_quote(val, option: option, force: force, double: double, override: override)
69
73
  else
70
- quote ? shell_quote(val, force: force, double: double, override: override) : val
74
+ val
71
75
  end}"
72
76
  end}"
73
77
  end
@@ -88,8 +92,8 @@ module Squared
88
92
  shell_escape(val.start_with?('-') ? val : "--#{val}", double: double)
89
93
  end
90
94
 
91
- def quote_option(flag, val, double: false)
92
- shell_option(flag, val, escape: false, double: double)
95
+ def quote_option(flag, val, option: true, double: false, merge: false)
96
+ shell_option(flag, val, escape: false, option: option, double: double, merge: merge)
93
97
  end
94
98
 
95
99
  def basic_option(flag, val, merge: false)
@@ -89,7 +89,7 @@ module Squared
89
89
  case link
90
90
  when 'hard', 1
91
91
  FileUtils.ln(src, dest, force: force, verbose: verbose)
92
- when true, 'soft', 0
92
+ when TrueClass, 'soft', 0
93
93
  FileUtils.ln_s(src, dest, force: force, verbose: verbose)
94
94
  else
95
95
  FileUtils.cp(src, dest, verbose: verbose)
@@ -131,7 +131,7 @@ module Squared
131
131
 
132
132
  def env_bool(key, default = false, suffix: nil, strict: false, index: false)
133
133
  case key
134
- when nil
134
+ when ::NilClass
135
135
  default
136
136
  when ::String
137
137
  case (val = env_value(key, suffix: suffix, strict: strict))
@@ -148,14 +148,6 @@ module Squared
148
148
  end
149
149
 
150
150
  def env_pipe(key, default = 1, suffix: nil, strict: false, root: nil)
151
- if default.is_a?(::String)
152
- begin
153
- default = (root ? Pathname.new(root).join(default) : Pathname.new(default)).realdirpath
154
- rescue StandardError => e
155
- default = 1
156
- warn e
157
- end
158
- end
159
151
  case key
160
152
  when ::String
161
153
  case (ret = env_value(key, suffix: suffix, strict: strict))
@@ -165,7 +157,16 @@ module Squared
165
157
  when ::Numeric
166
158
  return key if key.between?(0, 2)
167
159
  end
168
- default
160
+ begin
161
+ if default.is_a?(::String)
162
+ default = (root ? Pathname.new(root).join(default) : Pathname.new(default)).realdirpath
163
+ end
164
+ rescue StandardError => e
165
+ warn e
166
+ 1
167
+ else
168
+ default
169
+ end
169
170
  end
170
171
 
171
172
  def env_match(key, default = nil, suffix: nil, strict: false, options: 0, timeout: nil)
@@ -38,7 +38,6 @@ module Squared
38
38
  end
39
39
 
40
40
  @@mime_obj = {}
41
- @@task_desc = Rake::TaskManager.record_task_metadata
42
41
 
43
42
  attr_reader :main, :name, :project, :theme
44
43
  attr_accessor :pipe
@@ -306,7 +305,7 @@ module Squared
306
305
  end
307
306
 
308
307
  def task_desc(command, *ext, target: nil)
309
- return unless @@task_desc
308
+ return unless Rake::TaskManager.record_task_metadata
310
309
 
311
310
  val = "#{ext.first}[#{target ? '' : "file?=#{File.basename(main)}.#{ext.last},"}keys+]"
312
311
  args = *name.split(':').push(command, val)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.4.7'
4
+ VERSION = '0.4.8'
5
5
  end
@@ -15,7 +15,8 @@ module Squared
15
15
  global: false,
16
16
  env: false
17
17
  }.freeze
18
- private_constant :SCRIPT_OBJ
18
+ TASK_METADATA = Rake::TaskManager.record_task_metadata
19
+ private_constant :SCRIPT_OBJ, :TASK_METADATA
19
20
 
20
21
  class << self
21
22
  def implement(*objs, base: false)
@@ -51,6 +52,14 @@ module Squared
51
52
  end
52
53
  end
53
54
 
55
+ def exclude(*args)
56
+ @task_exclude.merge(args.map!(&:to_sym))
57
+ end
58
+
59
+ def series_wrap(app)
60
+ impl_series.new(app, exclude: @task_exclude.to_a)
61
+ end
62
+
54
63
  def baseref
55
64
  impl_project.ref
56
65
  end
@@ -64,7 +73,7 @@ module Squared
64
73
  end
65
74
 
66
75
  @kind_project = []
67
- @@task_desc = Rake::TaskManager.record_task_metadata
76
+ @task_exclude = SymSet.new
68
77
 
69
78
  attr_reader :root, :home, :main, :prefix, :exception, :warning, :pipe, :verbose, :theme, :series, :closed
70
79
 
@@ -81,7 +90,7 @@ module Squared
81
90
  @home.mkpath rescue nil
82
91
  @root = @home.parent
83
92
  @prefix = prefix
84
- @series = Application.impl_series.new(self)
93
+ @series = Application.series_wrap(self)
85
94
  @project = {}
86
95
  @kind = {}
87
96
  @extensions = []
@@ -184,36 +193,36 @@ module Squared
184
193
  self
185
194
  end
186
195
 
187
- def run(script, group: @group, ref: @ref, on: nil)
188
- script_command :run, script, group, ref, on
196
+ def run(script = nil, group: @group, ref: @ref, on: nil, &blk)
197
+ script_command :run, script, group, ref, on, &blk
189
198
  end
190
199
 
191
200
  def script(script, group: @group, ref: @ref, on: nil)
192
201
  script_command :script, script, group, ref, on
193
202
  end
194
203
 
195
- def depend(script, group: @group, ref: @ref, on: nil)
196
- script_command :depend, script, group, ref, on
204
+ def depend(script = nil, group: @group, ref: @ref, on: nil, &blk)
205
+ script_command :depend, script, group, ref, on, &blk
197
206
  end
198
207
 
199
208
  def graph(script, group: @group, ref: @ref, on: nil)
200
209
  script_command :graph, as_a(script, :to_s).freeze, group, ref, on
201
210
  end
202
211
 
203
- def clean(script, group: @group, ref: @ref, on: nil)
204
- script_command :clean, script, group, ref, on
212
+ def clean(script = nil, group: @group, ref: @ref, on: nil, &blk)
213
+ script_command :clean, script, group, ref, on, &blk
205
214
  end
206
215
 
207
- def doc(script, group: @group, ref: @ref, on: nil)
208
- script_command :doc, script, group, ref, on
216
+ def doc(script = nil, group: @group, ref: @ref, on: nil, &blk)
217
+ script_command :doc, script, group, ref, on, &blk
209
218
  end
210
219
 
211
- def lint(script, group: @group, ref: @ref, on: nil)
212
- script_command :lint, script, group, ref, on
220
+ def lint(script = nil, group: @group, ref: @ref, on: nil, &blk)
221
+ script_command :lint, script, group, ref, on, &blk
213
222
  end
214
223
 
215
- def test(script, group: @group, ref: @ref, on: nil)
216
- script_command :test, script, group, ref, on
224
+ def test(script = nil, group: @group, ref: @ref, on: nil, &blk)
225
+ script_command :test, script, group, ref, on, &blk
217
226
  end
218
227
 
219
228
  def log(script, group: @group, ref: @ref)
@@ -408,7 +417,7 @@ module Squared
408
417
  end
409
418
 
410
419
  def task_desc(*args, **kwargs)
411
- return unless @@task_desc
420
+ return unless TASK_METADATA
412
421
 
413
422
  name = kwargs.delete(:name)
414
423
  if @describe
@@ -422,7 +431,7 @@ module Squared
422
431
  @describe[:replace].each do |pat, tmpl|
423
432
  next unless val =~ pat
424
433
 
425
- val = replace.($~, tmpl.dup)
434
+ val = replace.call($~, tmpl.dup)
426
435
  found = true
427
436
  end
428
437
  if (out = @describe[:alias][val])
@@ -432,7 +441,7 @@ module Squared
432
441
  @describe[:pattern].each do |key, pat|
433
442
  next unless val =~ pat
434
443
 
435
- val = replace.($~, key.dup)
444
+ val = replace.call($~, key.dup)
436
445
  found = true
437
446
  break
438
447
  end
@@ -495,7 +504,7 @@ module Squared
495
504
  end
496
505
 
497
506
  def format_desc(val, opts = nil, arg: 'opts*', before: nil, after: nil, out: false)
498
- return unless @@task_desc || out
507
+ return unless TASK_METADATA || out
499
508
 
500
509
  val = val.to_s.split(':') if val.is_a?(String)
501
510
  if before || after || opts
@@ -552,6 +561,8 @@ module Squared
552
561
  end
553
562
 
554
563
  def task_include?(obj, key, ref = nil)
564
+ return false if @series.exclude.include?(key)
565
+
555
566
  task_base?(key) ? obj.has?(key, ref || baseref) : task_extend?(obj, key)
556
567
  end
557
568
 
@@ -643,7 +654,12 @@ module Squared
643
654
  puts_oe(*args, pipe: pipe)
644
655
  end
645
656
 
646
- def script_command(task, val, group, ref, on)
657
+ def script_command(task, val, group, ref, on, &blk)
658
+ if block_given?
659
+ val = Struct.new(:run, :block).new(val, blk)
660
+ elsif !val
661
+ return self
662
+ end
647
663
  if group
648
664
  label = :group
649
665
  items = as_a(group, :to_sym)