squared 0.7.1 → 0.7.3

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: 17d98e04832d29621058a1dafa1f9440f0734849aa2b68aecffed2b0c50c0e26
4
- data.tar.gz: 31391a2561c19025ddd25a20c980f10cf605de2593a24e793eee87ee3d1ccc67
3
+ metadata.gz: 82923f41d47824239a45fb31f694e85d30c1cf0f73c099df00d2d7a210f6b156
4
+ data.tar.gz: e80edb54f73431248fb26d11ff53c5424ae791ef28b5a5ddcc3bb3f0da20622b
5
5
  SHA512:
6
- metadata.gz: 246be3dde753890551351e232e8f804b7cf3e32ea610113983ad465dd863365f800142d18c91e732df7152e3cfdc6c8c727c980d85b1079dc7acc39ecde6a0aa
7
- data.tar.gz: 3a9f07b19f697678bacf6d15914df6c33f74e2aa9a0d9b5f10b033b10c29e3460b34e2da3890ae62ed655858d499fb34dae49098096c73b9a2279bb8f9745bd1
6
+ metadata.gz: cbb9aaa755322f262c820b8f0cabb8e9bf6596e440e4d660311b5804b5c4f347c478c3bc2fddafdeddf4ff9643642da8c74445c118028e6ea904f59909232360
7
+ data.tar.gz: d0457af211645c310de59ae11cb693fbdb1c0fddd49989f805317dffb9db251693da9268fa2ad25b1739e94216b243521044502051ac8dbd111a65efdc31b1c0
data/CHANGELOG.md CHANGED
@@ -1,6 +1,79 @@
1
1
  # Changelog
2
2
 
3
- ## [0.7.1] - 2025-01-20
3
+ ## [0.7.3] - 2026-03-11
4
+
5
+ ### Added
6
+
7
+ - Python command install action user for non-venv was implemented.
8
+ - Ruby command version can create .ruby-version in project directory.
9
+ - Ruby command reshim calls version manager to regenerate bin paths.
10
+ - Git command diff can be piped to an output file.
11
+
12
+ ### Changed
13
+
14
+ - Python command publish detects twine through the file system.
15
+ - Git revbuild can bypass checksum by project name using GIT_FORCE.
16
+ - Git internal data calls do not write to logs.
17
+ - Ruby method serve uses as default port 3000 due to Errno::EACCES.
18
+
19
+ ### Fixed
20
+
21
+ - Node command outdated using PNPM did not work in nested projects.
22
+
23
+ ## [0.6.11] - 2026-03-11
24
+
25
+ ### Changed
26
+
27
+ - Git internal data calls do not write to logs.
28
+
29
+ ## [0.5.22] - 2026-03-11
30
+
31
+ - Project base run command was not covered due to lack of type checking.
32
+
33
+ ## [0.4.36] - 2026-03-11
34
+
35
+ ### Added
36
+
37
+ - Python venv initialization installs setuptools when detected.
38
+ - Node task depend adds prod option flags when NODE_ENV=production.
39
+
40
+ ### Fixed
41
+
42
+ - OptionPartition methods with escape parameter were reordered.
43
+ - OptionPartition methods with quote parameter were revised.
44
+
45
+ ## [0.6.10] - 2025-02-23
46
+
47
+ ### Added
48
+
49
+ - Docker command image action [ls|rm|tag|save] can be filtered.
50
+ - Pip command options were updated to 26.1.
51
+
52
+ ### Changed
53
+
54
+ - Ruby property autodetect was converted into an accessor.
55
+ - Python property editable was converted into an accessor.
56
+
57
+ ### Fixed
58
+
59
+ - Gem command install did not respond to interactive prompt.
60
+
61
+ ## [0.7.2] - 2026-02-09
62
+
63
+ ### Added
64
+
65
+ - OptionPartition can parse flags with a custom switch or separator.
66
+ - Gem command uninstall and pristine are interactive.
67
+ - Docker command network action create was implemented.
68
+ - Python command serve using http.server module was implemented.
69
+ - Ruby command serve using webrick was implemented.
70
+ - Node command serve using filename or string was implemented.
71
+
72
+ ### Fixed
73
+
74
+ - Docker task clean does not run in parallel without ENV override.
75
+
76
+ ## [0.7.1] - 2026-01-20
4
77
 
5
78
  ### Added
6
79
 
@@ -14,7 +87,7 @@
14
87
 
15
88
  - Git task show was completely non-functional due to excessive formatting.
16
89
 
17
- ## [0.7.0] - 2025-01-07
90
+ ## [0.7.0] - 2026-01-07
18
91
 
19
92
  ### Added
20
93
 
@@ -87,7 +160,7 @@
87
160
  - Project base method command_args did not append prompted value.
88
161
  - Bundler command exec did not use correct delegate for delete.
89
162
 
90
- ## [0.6.9] - 2025-01-07
163
+ ## [0.6.9] - 2026-01-07
91
164
 
92
165
  ### Added
93
166
 
@@ -1653,7 +1726,7 @@
1653
1726
  - Rake did not set original rakefile when calling itself.
1654
1727
  - Extended tasks were not associated to their supporting class method.
1655
1728
 
1656
- ## [0.1.0] - 2024-12-7
1729
+ ## [0.1.0] - 2024-12-07
1657
1730
 
1658
1731
  ### Added
1659
1732
 
@@ -1674,14 +1747,18 @@
1674
1747
  - Git pull did not display colors for diff bar chart.
1675
1748
  - Git commit did not fetch latest refs before submitting.
1676
1749
 
1677
- ## [0.0.12] - 2024-12-1
1750
+ ## [0.0.12] - 2024-12-01
1678
1751
 
1679
1752
  ### Added
1680
1753
 
1681
1754
  - Changelog was created.
1682
1755
 
1756
+ [0.7.3]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.3
1757
+ [0.7.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.2
1683
1758
  [0.7.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.1
1684
1759
  [0.7.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.0
1760
+ [0.6.11]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.11
1761
+ [0.6.10]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.10
1685
1762
  [0.6.9]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.9
1686
1763
  [0.6.8]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.8
1687
1764
  [0.6.7]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.7
@@ -1692,6 +1769,7 @@
1692
1769
  [0.6.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.2
1693
1770
  [0.6.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.1
1694
1771
  [0.6.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.0
1772
+ [0.5.22]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.22
1695
1773
  [0.5.21]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.21
1696
1774
  [0.5.20]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.20
1697
1775
  [0.5.19]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.19
@@ -1714,6 +1792,7 @@
1714
1792
  [0.5.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.2-ruby
1715
1793
  [0.5.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.1-ruby
1716
1794
  [0.5.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.0-ruby
1795
+ [0.4.36]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.36
1717
1796
  [0.4.35]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.35
1718
1797
  [0.4.34]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.34
1719
1798
  [0.4.33]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.33
data/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  | 2025-01-07 | 0.2.0 | | 3.4.0 | |
12
12
  | 2025-02-07 | 0.3.0 | | 3.4.1 | |
13
13
  | 2025-03-06 | 0.4.0 | | 3.4.2 | * |
14
- | 2025-06-16 | 0.5.0 | 2.5.0 | 3.4.3 | * |
14
+ | 2025-06-16 | 0.5.0 | 2.5.0 | 3.4.4 | * |
15
15
  | ---------- | ----- | ----- | ----- | --- |
16
16
  | 2025-08-23 | 0.5.5 | | 3.4.5 | |
17
17
  | 2025-10-31 | 0.6.0 | | 3.4.7 | * |
@@ -300,10 +300,10 @@ Workspace::Application
300
300
  end
301
301
  .with(:python) do
302
302
  doc("make html")
303
- add("android-docs") do
303
+ add("android-docs", serve: "./build/html") do # Uses built-in http.server module (ruby: webrick)
304
304
  chain "all", "doc", with: "squared", after: "squared" # step: 4
305
305
  end
306
- add("chrome-docs") do
306
+ add("chrome-docs", serve: { root: "./build/html", port: 8000 }) do # root | bind | port | opts[]
307
307
  chain "all", "doc", with: "squared", before: "squared:revbuild" # Same
308
308
  end
309
309
  end
@@ -641,7 +641,7 @@ BANNER_${NAME}=0 #
641
641
  VERBOSE=0 # console output level
642
642
  VERBOSE_${NAME}=0 # 0,1,2,n
643
643
 
644
- REVBUILD_FORCE=1 # Rebuild all targets
644
+ REVBUILD_FORCE=1 # Rebuild all targets (GIT_FORCE)
645
645
  REVBUILD_FORCE_${NAME}=1 # Rebuild project
646
646
 
647
647
  PREREQS_${NAME}=build,copy # Class method name to invoke
@@ -749,12 +749,13 @@ docker build --no-cache --label=v1 --build-arg="NODE_TAG=24" --build-arg="RUBY_V
749
749
  | compose | run | VERSION=s |
750
750
  | compose | publish | TAG=s REGISTRY=s |
751
751
  | container | commit | REGISTRY=s PLATFORM=s DISABLE_CONTENT_TRUST=0,1 |
752
- | container | -run -create -exec | ALL=1 |
752
+ | container | -run -create -exec | ALL=1 FILTER=s |
753
753
  | | -update -commit | |
754
754
  | image | rm | Y=1 |
755
755
  | image | push | TAG=s REGISTRY=s |
756
756
  | image | -push | ALL=1 |
757
- | network | * | ALL=1 |
757
+ | image | -push -pull | FILTER=s |
758
+ | network | * | ALL=1 FILTER=s |
758
759
 
759
760
  ### asdf
760
761
 
@@ -10,7 +10,9 @@ module Squared
10
10
  private_constant :QUOTE_VALUE
11
11
 
12
12
  String.define_method(:stripquote) { sub(QUOTE_VALUE, '\2') }
13
- Array.define_method(:quote!) { |**kwargs| map { |s| Shell.shell_quote(s, **kwargs) } }
13
+ Array.define_method(:quote!) do |escape: false, **kwargs|
14
+ map { |s| escape && !Rake::Win32.windows? ? Shellwords.escape(s) : Shell.shell_quote(s, **kwargs) }
15
+ end
14
16
 
15
17
  module_function
16
18
 
@@ -73,7 +75,7 @@ module Squared
73
75
  end
74
76
 
75
77
  def shell_option(flag, val = nil, sep: '=', escape: true, quote: true, force: true, double: false, merge: false,
76
- override: false)
78
+ switch: nil, override: false)
77
79
  flag = flag.to_s
78
80
  if flag =~ QUOTE_VALUE
79
81
  double = $1 == '"'
@@ -88,6 +90,9 @@ module Squared
88
90
  else
89
91
  merge ? '' : ' '
90
92
  end
93
+ elsif switch
94
+ pre = switch unless flag.start_with?(switch)
95
+ merge ? '' : sep
91
96
  elsif flag.size == 1
92
97
  pre = '-'
93
98
  merge ? '' : ' '
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.7.1'
4
+ VERSION = '0.7.3'
5
5
  end
@@ -23,7 +23,7 @@ module Squared
23
23
 
24
24
  OPTIONS = Workspace::Support.hashobj
25
25
  VAR_SET = %i[parent global script index envname desc dependfile dependname dependindex theme archive env graph
26
- dev prod timeout pass only exclude asdf].freeze
26
+ dev prod timeout pass only exclude serve asdf].freeze
27
27
  BLK_SET = %i[run depend doc lint test copy clean].freeze
28
28
  SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+))?[-.]?(\S+)?\b/.freeze
29
29
  URI_SCHEME = %r{\A([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
@@ -117,7 +117,8 @@ module Squared
117
117
  subtasks({
118
118
  'graph' => %i[run print].freeze,
119
119
  'unpack' => %i[zip gz tar ext],
120
- 'asdf' => %i[set exec env current update latest where reshim]
120
+ 'asdf' => %i[set exec env current update latest where reshim],
121
+ 'serve' => nil
121
122
  })
122
123
 
123
124
  attr_reader :name, :workspace, :path, :theme, :group, :parent, :children, :dependfile,
@@ -276,80 +277,114 @@ module Squared
276
277
  Base.subtasks do |action, flags|
277
278
  next if task_pass?(action)
278
279
 
279
- namespace action do
280
- flags.each do |flag|
281
- case action
282
- when 'graph'
283
- break unless graph?
284
-
285
- format_desc action, flag, '(-)project*'
286
- task flag do |_, args|
287
- args = args.to_a.reject { |val| val == name }
288
- if flag == :run
289
- graph args
290
- else
291
- out = graph(args, out: [], order: {})
292
- emphasize(out, title: path, right: true, border: borderstyle, sub: [
293
- opt_style(theme[:header], /\A(#{Regexp.escape(path.to_s)})(.*)\z/),
294
- opt_style(theme[:active], /\A(#{Regexp.escape(name)})(.*)\z/),
295
- opt_style(theme[:inline], /\A((?~ \() \()(\d+)(\).*)\z/, 2)
296
- ])
280
+ if flags.nil?
281
+ case action
282
+ when 'serve'
283
+ next unless serve?
284
+
285
+ format_desc action, nil, 'root,port?,bind?,opts*'
286
+ task action, [:root] do |_, args|
287
+ if !args.root && @serve
288
+ kwargs = @serve.dup
289
+ root = kwargs.delete(:root)
290
+ opts = kwargs.delete(:opts) || []
291
+ else
292
+ root = param_guard(action, 'path', args: args, key: :root)
293
+ opts = args.extras
294
+ unless opts.empty?
295
+ port = opts.shift
296
+ if port.match?(/^\d+$/)
297
+ bind = opts.shift
298
+ else
299
+ bind = port
300
+ port = nil
301
+ end
302
+ if bind&.start_with?('-')
303
+ opts << bind
304
+ bind = nil
305
+ end
297
306
  end
307
+ kwargs = { port: port, bind: bind }.compact
298
308
  end
299
- when 'unpack'
300
- format_desc(action, flag, 'tag/url,dir,digest?,f/orce?', before: ('ext' if flag == :ext))
301
- params = %i[tag dir digest force]
302
- params.unshift(:ext) if flag == :ext
303
- task flag, params do |_, args|
304
- ext = flag == :ext ? param_guard(action, flag, args: args, key: :ext) : flag.to_s
305
- tag = param_guard(action, flag, args: args, key: :tag)
306
- dir = param_guard(action, flag, args: args, key: :dir)
307
- unless tag.match?(URI_SCHEME)
308
- tag = unpack_get tag, ext
309
- tag ||= if @release
310
- "%s.#{ext}" % [@release.include?('??') ? @release.sub('??', tag) : @release + tag]
309
+ serve(basepath(root).to_s, opts, **kwargs)
310
+ end
311
+ end
312
+ else
313
+ namespace action do
314
+ flags.each do |flag|
315
+ case action
316
+ when 'graph'
317
+ break unless graph?
318
+
319
+ format_desc action, flag, '(-)project*'
320
+ task flag do |_, args|
321
+ args = args.to_a.reject { |val| val == name }
322
+ if flag == :run
323
+ graph args
324
+ else
325
+ out = graph(args, out: [], order: {})
326
+ emphasize(out, title: path, right: true, border: borderstyle, sub: [
327
+ opt_style(theme[:header], /\A(#{Regexp.escape(path.to_s)})(.*)\z/),
328
+ opt_style(theme[:active], /\A(#{Regexp.escape(name)})(.*)\z/),
329
+ opt_style(theme[:inline], /\A((?~ \() \()(\d+)(\).*)\z/, 2)
330
+ ])
331
+ end
332
+ end
333
+ when 'unpack'
334
+ format_desc(action, flag, 'tag/url,dir,digest?,f/orce?', before: ('ext' if flag == :ext))
335
+ params = %i[tag dir digest force]
336
+ params.unshift(:ext) if flag == :ext
337
+ task flag, params do |_, args|
338
+ ext = flag == :ext ? param_guard(action, flag, args: args, key: :ext) : flag.to_s
339
+ tag = param_guard(action, flag, args: args, key: :tag)
340
+ dir = param_guard(action, flag, args: args, key: :dir)
341
+ unless tag.match?(URI_SCHEME)
342
+ tag = unpack_get tag, ext
343
+ tag ||= if @release
344
+ "%s.#{ext}" % [@release.include?('??') ? @release.sub('??', tag) : @release + tag]
345
+ else
346
+ raise_error ArgumentError, "no base uri: #{tag}", hint: ext
347
+ end
348
+ end
349
+ force = case (digest = args.digest)
350
+ when 'f', 'force'
351
+ digest = nil
352
+ true
311
353
  else
312
- raise_error ArgumentError, "no base uri: #{tag}", hint: ext
354
+ args.fetch(:force, false)
313
355
  end
356
+ unpack(basepath(dir), uri: tag, digest: digest, ext: ext, force: force)
314
357
  end
315
- force = case (digest = args.digest)
316
- when 'f', 'force'
317
- digest = nil
318
- true
319
- else
320
- args.fetch(:force, false)
321
- end
322
- unpack(basepath(dir), uri: tag, digest: digest, ext: ext, force: force)
323
- end
324
- when 'asdf'
325
- break unless @asdf
326
-
327
- case flag
328
- when :set
329
- format_desc action, flag, 'version,dir?=u/home|p/arent'
330
- task flag, [:version] do |_, args|
331
- args = if (version = args.version)
332
- args.extras
333
- else
334
- version, opts = choice_index('Select a version',
335
- @asdf[1].children
336
- .map(&:basename)
337
- .sort { |a, b| b <=> a }
338
- .push('latest', 'system'),
339
- accept: [accept_y('Confirm?')],
340
- values: 'Options')
341
- OptionPartition.strip(opts)
342
- end
343
- asdf(flag, args, version: version)
344
- end
345
- else
346
- format_desc(action, flag, case flag when :exec, :env then 'command' end)
347
- task flag do |_, args|
348
- args = args.to_a
349
- if args.empty? && (flag == :exec || flag == :env)
350
- args << readline('Enter command', force: flag == :exec)
358
+ when 'asdf'
359
+ break unless @asdf
360
+
361
+ case flag
362
+ when :set
363
+ format_desc action, flag, 'version,dir?=u/home|p/arent'
364
+ task flag, [:version] do |_, args|
365
+ args = if (version = args.version)
366
+ args.extras
367
+ else
368
+ version, opts = choice_index('Select a version',
369
+ @asdf[1].children
370
+ .map(&:basename)
371
+ .sort { |a, b| b <=> a }
372
+ .push('latest', 'system'),
373
+ accept: [accept_y('Confirm?')],
374
+ values: 'Options')
375
+ OptionPartition.strip(opts)
376
+ end
377
+ asdf(flag, args, version: version)
378
+ end
379
+ else
380
+ format_desc(action, flag, case flag when :exec, :env then 'command' end)
381
+ task flag do |_, args|
382
+ args = args.to_a
383
+ if args.empty? && (flag == :exec || flag == :env)
384
+ args << readline('Enter command', force: flag == :exec)
385
+ end
386
+ asdf flag, args
351
387
  end
352
- asdf flag, args
353
388
  end
354
389
  end
355
390
  end
@@ -431,7 +466,7 @@ module Squared
431
466
  args[0] = instance_eval(&blk) || f
432
467
  return unless args.first
433
468
  end
434
- if args.all? { |val| val.is_a?(Array) }
469
+ if args.all?(Array)
435
470
  var = {}
436
471
  cmd = args.each_with_object([]) do |val, out|
437
472
  case val.first
@@ -747,19 +782,18 @@ module Squared
747
782
  ensure
748
783
  if dir
749
784
  remove_entry dir
750
- elsif delete && file&.exist?
785
+ elsif delete && file.exist?
751
786
  file.unlink
752
787
  end
753
788
  end
754
789
  end
755
790
 
756
- def asdf(flag, opts = [], version: nil)
791
+ def asdf(flag, opts = [], name: nil, version: nil, banner: true)
757
792
  return unless @asdf
758
793
 
759
794
  cmd = flag == :update ? session('asdf', 'plugin update') : session('asdf', flag)
760
- name = @asdf.first
795
+ name ||= @asdf.first
761
796
  legacy = @@asdf.version == 15
762
- banner = true
763
797
  case flag
764
798
  when :set
765
799
  u = has_value?(opts, 'u', 'home')
@@ -782,7 +816,7 @@ module Squared
782
816
  cmd << name
783
817
  banner = false if flag == :latest || flag == :where
784
818
  end
785
- success?(run(banner: banner, from: symjoin('asdf', flag)), flag == :set || flag == :reshim)
819
+ success?(run(banner: banner, from: symjoin('asdf', flag)), banner, flag == :set || flag == :reshim)
786
820
  end
787
821
 
788
822
  def first(key, *args, **kwargs, &blk)
@@ -959,6 +993,8 @@ module Squared
959
993
  parent_set val
960
994
  when :archive
961
995
  archive_set val
996
+ when :serve
997
+ serve_set val
962
998
  when :asdf
963
999
  asdf_set val
964
1000
  when :run
@@ -1244,7 +1280,7 @@ module Squared
1244
1280
  print_error e
1245
1281
  end
1246
1282
  log[:progname] ||= @name
1247
- env('LOG_LEVEL', ignore: false) { |val| log[:level] = val.start_with?(/\d/) ? log_sym(val.to_i) : val }
1283
+ env('LOG_LEVEL', ignore: false) { |s| log[:level] = s.start_with?(/\d/) ? log_sym(s.to_i) : s }
1248
1284
  log.delete(:file)
1249
1285
  @log = [file, log]
1250
1286
  end
@@ -1423,17 +1459,17 @@ module Squared
1423
1459
  s += "#{indent || (last && data[final].last == kwargs[:context]) ? ' ' : a} "
1424
1460
  k += 1
1425
1461
  end
1426
- s += "#{j ? d : c}#{b * 3} #{tag.call(proj)}"
1462
+ s + "#{j ? d : c}#{b * 3} #{tag.call(proj)}"
1427
1463
  end
1428
1464
  end
1429
1465
  if order
1430
1466
  n = order.size
1431
1467
  order[name] ||= if proj.parent
1432
- if order[s = proj.parent.name]
1433
- order[s] += 1
1468
+ if order[key = proj.parent.name]
1469
+ order[key] += 1
1434
1470
  n.pred
1435
1471
  else
1436
- order[s] = n.succ
1472
+ order[key] = n.succ
1437
1473
  n
1438
1474
  end
1439
1475
  else
@@ -2007,10 +2043,12 @@ module Squared
2007
2043
  [shell_bin(a), b].compact.join(' ')
2008
2044
  end
2009
2045
 
2010
- def parse_json(val, kind: Hash, hint: nil)
2046
+ def parse_json(val, kind: Hash, key: nil, hint: nil, &blk)
2011
2047
  ret = JSON.parse(val)
2012
2048
  raise_error 'invalid JSON'.subhint(kind.name), val, hint: hint if kind && !ret.is_a?(kind)
2013
- ret
2049
+ return ret unless key
2050
+
2051
+ block_given? ? ret.fetch(key, &blk) : ret[key]
2014
2052
  rescue => e
2015
2053
  print_error(kind ? Logger::ERROR : Logger::INFO, e, subject: name)
2016
2054
  end
@@ -2384,7 +2422,7 @@ module Squared
2384
2422
  ret = []
2385
2423
  if data[:command]
2386
2424
  ret[0] = data[:command]
2387
- ret[1] = data[:opts] unless diso
2425
+ ret[1] = data[:opts] unless noopt
2388
2426
  ret[3] = data[:args]
2389
2427
  elsif data[:script]
2390
2428
  ret[1] = data[:script]
@@ -2393,7 +2431,7 @@ module Squared
2393
2431
  else
2394
2432
  ret[0] = false
2395
2433
  end
2396
- ret[2] = data[:env] unless dise
2434
+ ret[2] = data[:env] unless noenv
2397
2435
  ret
2398
2436
  end
2399
2437
  case global
@@ -2408,7 +2446,7 @@ module Squared
2408
2446
  when Hash
2409
2447
  @output = parse.call(data)
2410
2448
  when Enumerable
2411
- @output = if cmd.all? { |data| data.is_a?(Hash) }
2449
+ @output = if cmd.all?(Hash)
2412
2450
  noopt = false
2413
2451
  noenv = false
2414
2452
  cmd.map { |data| parse.call(data) }
@@ -2499,6 +2537,17 @@ module Squared
2499
2537
  end
2500
2538
  end
2501
2539
 
2540
+ def serve_set(val)
2541
+ @serve = case val
2542
+ when false
2543
+ false
2544
+ when String
2545
+ { root: val }
2546
+ when Hash
2547
+ val if val.key?(:root)
2548
+ end
2549
+ end
2550
+
2502
2551
  def asdf_set(val)
2503
2552
  @asdf = if @@asdf && val
2504
2553
  dir = @@asdf.path.join('installs', val)
@@ -2660,6 +2709,10 @@ module Squared
2660
2709
  level.empty? ? ex != false && ex != Logger::INFO : ex.is_a?(Numeric) && level.include?(ex)
2661
2710
  end
2662
2711
 
2712
+ def serve?
2713
+ false
2714
+ end
2715
+
2663
2716
  def variables
2664
2717
  VAR_SET
2665
2718
  end