squared 0.7.0 → 0.7.2

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: 77cd278e7e9855e6601d497f876db37b4e118e8b13af1ef2535eff1ef5799f84
4
- data.tar.gz: e5ef173753eff1f9de0a31f0a0db590ec45f8c0d6e1d0b79f7168160c8b03204
3
+ metadata.gz: a9e8d3d41e090465c85e45088c984711f2a26f6ab2f38d56ed8b58658735f678
4
+ data.tar.gz: 2ff93124747a950b6ac94424052268e4ab6b107e934a2fd16478ca03d74a5409
5
5
  SHA512:
6
- metadata.gz: 73c1cb07ee0f10476bd47493455231341bd4a3e42107e74c5e16123ddf99e832cc2c96b870615c0ff5a632a1de4ac9176a9d8cb0b1be460018699140f1c15b48
7
- data.tar.gz: 365c543051f2a2fc01f445c3e3f3f8b2162e6d0ad0663b91f0928667504193511f59e55683d1b832f56e82d20ef2adc7033673d888a366ab01ed12256504af33
6
+ metadata.gz: 60b75a0d2d6c361ccf9341496b0b4e9e3a0599030ea6099f50a2819246111ff671a8f52cf583e91294593179949542f1e18814059c953007bbee528c1dbaa244
7
+ data.tar.gz: c52d560aacea0d6279948db05d29c1a5b5c55a5d7fb2218f03d418122417a8ca3377a1b4af54238538d4d911c4827b6a2921b76001b161a6bfdd460e55dd13e5
data/CHANGELOG.md CHANGED
@@ -1,6 +1,35 @@
1
1
  # Changelog
2
2
 
3
- ## [0.7.0] - 2025-01-07
3
+ ## [0.7.2] - 2026-02-09
4
+
5
+ ### Added
6
+
7
+ - OptionPartition can parse flags with a custom switch or separator.
8
+ - Gem command uninstall and pristine are interactive.
9
+ - Docker command network action create was implemented.
10
+ - Python command serve using http.server module was implemented.
11
+ - Ruby command serve using webrick was implemented.
12
+ - Node command serve using filename or string was implemented.
13
+
14
+ ### Fixed
15
+
16
+ - Docker task clean does not run in parallel without ENV override.
17
+
18
+ ## [0.7.1] - 2026-01-20
19
+
20
+ ### Added
21
+
22
+ - Git task revbuild can run auxiliary tasks before and after changes.
23
+
24
+ ### Changed
25
+
26
+ - Git task revbuild can be configured to not accept a failed run.
27
+
28
+ ### Fixed
29
+
30
+ - Git task show was completely non-functional due to excessive formatting.
31
+
32
+ ## [0.7.0] - 2026-01-07
4
33
 
5
34
  ### Added
6
35
 
@@ -73,7 +102,7 @@
73
102
  - Project base method command_args did not append prompted value.
74
103
  - Bundler command exec did not use correct delegate for delete.
75
104
 
76
- ## [0.6.9] - 2025-01-07
105
+ ## [0.6.9] - 2026-01-07
77
106
 
78
107
  ### Added
79
108
 
@@ -1666,6 +1695,8 @@
1666
1695
 
1667
1696
  - Changelog was created.
1668
1697
 
1698
+ [0.7.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.2
1699
+ [0.7.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.1
1669
1700
  [0.7.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.0
1670
1701
  [0.6.9]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.9
1671
1702
  [0.6.8]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.8
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 | * |
@@ -246,12 +246,15 @@ Workspace::Application
246
246
  }
247
247
  )
248
248
  .with(:node) do # rake clone:node
249
- add("e-mc", "emc") # https://github.com/anpham6/e-mc
249
+ add("e-mc", "emc") do # https://github.com/anpham6/e-mc
250
+ revbuild(before: %w[status squared:status], # emc:status + squared:status (always)
251
+ after: "refresh") # emc:refresh (changed)
252
+ end #
250
253
  add("pi-r", "pir") # https://github.com/anpham6/pi-r
251
254
  add("squared") # https://github.com/anpham6/squared
252
255
  end
253
256
  .with(:python) do # rake clone:python
254
- add("android-docs")
257
+ add("android-docs") { revbuild(pass: false) } # Do not update status on a failed run
255
258
  add("chrome-docs") do
256
259
  revbuild(include: "source/", exclude: ["source/conf.py"]) # Limit files being watched
257
260
  end
@@ -297,10 +300,10 @@ Workspace::Application
297
300
  end
298
301
  .with(:python) do
299
302
  doc("make html")
300
- add("android-docs") do
303
+ add("android-docs", serve: "./build/html") do # Uses built-in http.server module (ruby: webrick)
301
304
  chain "all", "doc", with: "squared", after: "squared" # step: 4
302
305
  end
303
- add("chrome-docs") do
306
+ add("chrome-docs", serve: { root: "./build/html", port: 8000 }) do # root | bind | port | opts[]
304
307
  chain "all", "doc", with: "squared", before: "squared:revbuild" # Same
305
308
  end
306
309
  end
@@ -844,7 +847,7 @@ REPO_MANIFEST # e.g. latest,nightly,prod
844
847
  REPO_GROUPS # e.g. base,prod,docs
845
848
  REPO_STAGE # 0,1|sync,2|depend,4|build,8|copy,16|lint,512|dev
846
849
  REPO_SUBMODULES # 0,1
847
- REPO_Y # 0,1
850
+ REPO_Y # 0,1 (bypass interactive prompt)
848
851
  REPO_TIMEOUT # confirm dialog (seconds)
849
852
  ```
850
853
 
@@ -874,7 +877,7 @@ Features can be enabled through ENV when calling global tasks such as through *C
874
877
  | :------------- | :------------- | :----------------------------------------------- |
875
878
  | depend | - | FORCE CI IGNORE_SCRIPTS |
876
879
  | outdated | - | U|UPDATE=major|minor|patch DIFF DRY_RUN |
877
- | publish | - | OTP=s TAG=s ACCESS=0,1,s DRY_RUN |
880
+ | publish | - | OTP=s TAG=s ACCESS=0,1,s DRY_RUN Y |
878
881
  | depend package | * | PACAKGE_LOCK|LOCKFILE=0 NO_LOCKFILE=1 Y |
879
882
  | npm pnpm | depend package | CPU=s OS=s LIBC=s |
880
883
  | npm | package | SAVE IGNORE_SCRIPTS STRICT_PEER_DEPS |
@@ -73,7 +73,7 @@ module Squared
73
73
  end
74
74
 
75
75
  def shell_option(flag, val = nil, sep: '=', escape: true, quote: true, force: true, double: false, merge: false,
76
- override: false)
76
+ switch: nil, override: false)
77
77
  flag = flag.to_s
78
78
  if flag =~ QUOTE_VALUE
79
79
  double = $1 == '"'
@@ -88,6 +88,9 @@ module Squared
88
88
  else
89
89
  merge ? '' : ' '
90
90
  end
91
+ elsif switch
92
+ pre = switch unless flag.start_with?(switch)
93
+ merge ? '' : sep
91
94
  elsif flag.size == 1
92
95
  pre = '-'
93
96
  merge ? '' : ' '
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.7.0'
4
+ VERSION = '0.7.2'
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
@@ -959,6 +994,8 @@ module Squared
959
994
  parent_set val
960
995
  when :archive
961
996
  archive_set val
997
+ when :serve
998
+ serve_set val
962
999
  when :asdf
963
1000
  asdf_set val
964
1001
  when :run
@@ -1339,6 +1376,24 @@ module Squared
1339
1376
  end
1340
1377
  end
1341
1378
 
1379
+ def run_p(*cmd, sync: true, banner: verbose?, from: nil, **kwargs)
1380
+ case cmd.last
1381
+ when Hash, true, false
1382
+ var = cmd.pop
1383
+ end
1384
+ ws = workspace
1385
+ cmd.each do |meth|
1386
+ meth = meth.to_s
1387
+ if !meth.include?(':') && has?(meth) && respond_to?(meth)
1388
+ __send__(meth, sync: sync)
1389
+ elsif ws.task_defined?(val = task_join(name, meth)) || (meth.include?(':') && ws.task_defined?(val = meth))
1390
+ run(val, var, sync: sync, banner: banner, **kwargs)
1391
+ elsif from
1392
+ print_error(from, "method: #{meth}", subject: name, hint: 'undefined')
1393
+ end
1394
+ end
1395
+ end
1396
+
1342
1397
  def graph_branch(target, data, tasks = nil, out = nil, sync: true, pass: [], done: [], depth: 0, single: false,
1343
1398
  last: false, order: nil, **kwargs)
1344
1399
  tag = ->(proj) { "#{proj.name}#{"@#{proj.version}" if SEM_VER.match?(proj.version)}" }
@@ -1436,7 +1491,7 @@ module Squared
1436
1491
  run(cmd, sync: false, banner: false)
1437
1492
  ENV.delete(key) if key
1438
1493
  elsif proj.has?(meth, tasks || graph ? nil : workspace.baseref)
1439
- proj.__send__(meth.to_sym, sync: sync)
1494
+ proj.__send__(meth, sync: sync)
1440
1495
  end
1441
1496
  end
1442
1497
  end
@@ -1709,7 +1764,7 @@ module Squared
1709
1764
  ret.join("\n")
1710
1765
  end
1711
1766
 
1712
- def print_status(*args, from: :completed, **kwargs)
1767
+ def print_status(*args, loglevel: Logger::INFO, from: :completed, **kwargs)
1713
1768
  return if stdin? || silent?
1714
1769
 
1715
1770
  case from
@@ -1718,10 +1773,10 @@ module Squared
1718
1773
  sub_style!(out[1], **opt_style(theme[:major], /^( +major )(\d+)(.+)$/, 2))
1719
1774
  sub_style!(out[1], **opt_style(theme[:active], /^(.+)(minor )(\d+)(.+)$/, 3))
1720
1775
  sub_style!(out[1], **opt_style(theme[:current], /^(.+)(patch )(\d+)(.+)$/, 3)) if theme[:current]
1721
- when :completed
1776
+ when :completed, :failed
1722
1777
  return unless kwargs[:start]
1723
1778
 
1724
- out = log_message(Logger::INFO, *args, sub_style('completed', theme[:active]),
1779
+ out = log_message(loglevel, *args, sub_style(from.to_s, theme[:active]),
1725
1780
  subject: kwargs[:subject], hint: time_format(time_epoch - kwargs[:start]))
1726
1781
  else
1727
1782
  return
@@ -2017,8 +2072,10 @@ module Squared
2017
2072
  args
2018
2073
  end
2019
2074
 
2020
- def confirm_basic(msg, target, default = 'Y', style: :inline, **kwargs)
2021
- confirm("#{msg} [#{sub_style(target.to_s, style.is_a?(Symbol) ? theme[style] : style)}]", default, **kwargs)
2075
+ def confirm_basic(msg, hint, default = 'Y', style: :inline, target: @session, prefix: nil, **kwargs)
2076
+ return true if prefix ? option('y', prefix: prefix) : target && option('y', target: target)
2077
+
2078
+ confirm("#{msg} [#{sub_style(hint.to_s, style.is_a?(Symbol) ? theme[style] : style)}]", default, **kwargs)
2022
2079
  end
2023
2080
 
2024
2081
  def confirm_semver(msg, type, style: (type == 1 && theme[:major]) || :inline, timeout: 0, **kwargs)
@@ -2479,6 +2536,17 @@ module Squared
2479
2536
  end
2480
2537
  end
2481
2538
 
2539
+ def serve_set(val)
2540
+ @serve = case val
2541
+ when false
2542
+ false
2543
+ when String
2544
+ { root: val }
2545
+ when Hash
2546
+ val if val.key?(:root)
2547
+ end
2548
+ end
2549
+
2482
2550
  def asdf_set(val)
2483
2551
  @asdf = if @@asdf && val
2484
2552
  dir = @@asdf.path.join('installs', val)
@@ -2640,6 +2708,10 @@ module Squared
2640
2708
  level.empty? ? ex != false && ex != Logger::INFO : ex.is_a?(Numeric) && level.include?(ex)
2641
2709
  end
2642
2710
 
2711
+ def serve?
2712
+ false
2713
+ end
2714
+
2643
2715
  def variables
2644
2716
  VAR_SET
2645
2717
  end
@@ -72,6 +72,8 @@ module Squared
72
72
  }.freeze,
73
73
  network: {
74
74
  connect: %w[alias=b driver-opt=q gw-priority=n ip=b ip6=q link=b link-local-ip=q].freeze,
75
+ create: %w[attachable config-only gateway ingress internal ipv4 ipv6 aux-address=q config-from=b d|driver=b
76
+ ip-range=q ipam-driver=b ipam-opt=q label=q o|opt=q scope=b subnet=q].freeze,
75
77
  disconnect: %w[f|force].freeze
76
78
  }.freeze
77
79
  }.freeze
@@ -114,7 +116,7 @@ module Squared
114
116
  'image' => %i[ls rm pull push tag save].freeze,
115
117
  'container' => %i[run create exec update commit inspect diff start stop restart pause unpause top stats kill
116
118
  rm].freeze,
117
- 'network' => %i[connect disconnect].freeze,
119
+ 'network' => %i[connect disconnect create].freeze,
118
120
  'ls' => nil
119
121
  })
120
122
 
@@ -312,8 +314,9 @@ module Squared
312
314
  when 'network'
313
315
  format_desc action, flag, 'target,opts*'
314
316
  task flag, [:target] do |_, args|
315
- if args.target
316
- network(flag, args.extras, target: args.target)
317
+ target = flag == :create ? param_guard(action, flag, args: args, key: :target) : args.target
318
+ if target
319
+ network(flag, args.extras, target: target)
317
320
  else
318
321
  choice_command flag
319
322
  end
@@ -329,8 +332,8 @@ module Squared
329
332
  def clean(*, sync: invoked_sync?('clean'), **)
330
333
  if runnable?(@clean)
331
334
  super
332
- else
333
- image(:rm, sync: sync)
335
+ elsif sync || option('y', prefix: 'docker')
336
+ image :rm
334
337
  end
335
338
  end
336
339
 
@@ -635,10 +638,17 @@ module Squared
635
638
 
636
639
  def network(flag, opts = [], target: nil)
637
640
  cmd, opts = docker_session('network', flag, opts: opts)
638
- OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self)
639
- .clear
641
+ op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self)
642
+ .clear
640
643
  from = symjoin 'network', flag
641
- list_image(flag, docker_output('ps -a'), from: from) { |id| success?(run(cmd.temp(target, id), from: from)) }
644
+ if flag == :create
645
+ op.add_quote(target)
646
+ run(from: from)
647
+ else
648
+ list_image(flag, docker_output('ps -a'), from: from) do |id|
649
+ success?(run(cmd.temp(target, id), from: from))
650
+ end
651
+ end
642
652
  end
643
653
 
644
654
  def build?
@@ -1108,15 +1108,16 @@ module Squared
1108
1108
  pull(:autostash, sync: sync)
1109
1109
  end
1110
1110
 
1111
- def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
1111
+ def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil,
1112
+ banner: !sync || verbose? || task_invoked?('fetch'))
1112
1113
  opts = git_session('fetch', opts: opts).last
1113
1114
  opts << 'all' if flag == :all || option('all')
1114
1115
  append_pull(opts, collect_hash(OPT_GIT[:fetch]), flag: flag, from: :fetch, remote: remote,
1115
1116
  no: collect_hash(OPT_GIT[:no][:fetch]))
1116
1117
  if sync
1117
- source
1118
+ source(banner: banner)
1118
1119
  else
1119
- source(sync: false, **threadargs)
1120
+ source(sync: false, banner: banner, **threadargs)
1120
1121
  end
1121
1122
  end
1122
1123
 
@@ -1278,12 +1279,24 @@ module Squared
1278
1279
  kw = lambda do
1279
1280
  {
1280
1281
  include: relativepath(*Array(kwargs[:include]), all: true),
1281
- exclude: relativepath(*Array(kwargs[:exclude]), all: true)
1282
+ exclude: relativepath(*Array(kwargs[:exclude]), all: true),
1283
+ before: kwargs[:before],
1284
+ after: kwargs[:after],
1285
+ pass: kwargs.fetch(:pass, true)
1282
1286
  }
1283
1287
  end
1284
1288
  unless workspace.closed
1285
1289
  if @revbuild
1286
- kw.call.each { |key, val| @revbuild[key] += val }
1290
+ kw.call.each do |key, val|
1291
+ case @revbuild[key]
1292
+ when Array
1293
+ @revbuild[key].concat(val)
1294
+ when Hash
1295
+ @revbuild[key].update(val)
1296
+ else
1297
+ @revbuild[key] = val
1298
+ end
1299
+ end
1287
1300
  else
1288
1301
  @revbuild = kw.call
1289
1302
  end
@@ -1293,7 +1306,7 @@ module Squared
1293
1306
  return if sha.empty?
1294
1307
 
1295
1308
  sync = kwargs.fetch(:sync) { invoked_sync?('revbuild', flag) }
1296
- kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? kw.call : @revbuild || {}
1309
+ kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? kw.call : @revbuild || { pass: true }
1297
1310
  case flag
1298
1311
  when :build
1299
1312
  op = OptionPartition.new(opts, VAL_GIT[:revbuild].map { |key| "#{key}=b?" }, project: self)
@@ -1306,6 +1319,7 @@ module Squared
1306
1319
  .compact
1307
1320
  OptionPartition.uniq!(args)
1308
1321
  end
1322
+ run_p(*Array(kwargs[:before]), sync: sync, from: :revbuild) if kwargs[:before]
1309
1323
  if (cur = workspace.rev_entry(name)) && cur['revision'] == sha && !env('REVBUILD_FORCE')
1310
1324
  files = status_digest(*args, **kwargs)
1311
1325
  if cur['files'].size == files.size && cur['files'].find { |key, val| files[key] != val }.nil?
@@ -1315,13 +1329,19 @@ module Squared
1315
1329
  end
1316
1330
  end
1317
1331
  start = time_epoch
1318
- build(*@output, sync: sync, from: symjoin('git', 'revbuild'))
1332
+ ret = build(*@output, sync: sync, from: symjoin('git', 'revbuild'))
1319
1333
  rescue => e
1320
1334
  print_error(e, pass: true)
1321
1335
  else
1322
- print_status('revbuild', subject: name, start: start)
1323
- workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1324
- sync: sync, utc: 'build')
1336
+ if ret == false && !kwargs[:pass]
1337
+ print_status('revbuild', subject: name, start: start, loglevel: Logger::WARN, from: :failed)
1338
+ workspace.rev_timeutc name, 'build'
1339
+ else
1340
+ run_p(*Array(kwargs[:after]), sync: sync, from: :revbuild) if kwargs[:after] && ret != false
1341
+ print_status('revbuild', subject: name, start: start)
1342
+ workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1343
+ sync: sync, utc: 'build')
1344
+ end
1325
1345
  end
1326
1346
 
1327
1347
  def reset(flag, opts = [], refs: nil, ref: nil, mode: nil, commit: nil)
@@ -1781,8 +1801,9 @@ module Squared
1781
1801
  opts << format if format
1782
1802
  end
1783
1803
  list = OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff] + OPT_GIT[:log][:diff_context]
1784
- op = OptionPartition.new(opts, list, cmd, project: self, pass: [:base],
1785
- no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log]))
1804
+ op = OptionPartition.new(opts, list, cmd,
1805
+ project: self,
1806
+ no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base]))
1786
1807
  op.append(delim: true)
1787
1808
  source(exception: false, banner: flag != :oneline)
1788
1809
  end
@@ -106,7 +106,21 @@ module Squared
106
106
  tsc: %w[excludeDirectories excludeFiles customConditions lib moduleSuffixes plugins rootDirs typeRoots
107
107
  types].freeze
108
108
  }.freeze
109
- private_constant :OPT_NPM, :OPT_PNPM, :OPT_YARN, :OPT_BERRY, :OPT_TSC, :PASS_NODE
109
+ APP_SERVE = 'const t=require("http"),e=require("fs"),a=require("path"),i="$ROOT",o={avif:"image/avif",css:"te' \
110
+ 'xt/css",gif:"image/gif",htm:"text/html",html:"text/html",ico:"image/x-icon",jpeg:"image/jpeg",jp' \
111
+ 'g:"image/jpeg",js:"text/javascript",mjs:"application/javascript",json:"application/json",md:"tex' \
112
+ 't/markdown",m4a:"audio/x-m4a",mp3:"audio/mpeg",mp4:"video/mp4",otf:"font/otf",pdf:"application/p' \
113
+ 'df",png:"image/png",rss:"application/rss+xml",svg:"image/svg+xml",ttc:"font/collection",ttf:"fon' \
114
+ 't/ttf",txt:"text/plain",wasm:"application/wasm",webp:"image/webp",woff:"font/woff",woff2:"font/w' \
115
+ 'off2",xml:"application/xml",webmanifest:"application/manifest+json",yaml:"text/yaml",yml:"text/y' \
116
+ 'aml"};function n(t){return o[a.extname(t).slice(1)]||"application/octet-stream"}t.createServer((' \
117
+ 't,o)=>{let l=a.join(i,t.url.split("?")[0]);try{e.statSync(l).isDirectory()&&(l=a.join(l,"index.h' \
118
+ 'tml"))}catch{}e.readFile(l,(t,e)=>{console.error(`[${(new Date).toLocaleTimeString().padStart(11' \
119
+ ', 0)}] ${t?"\x1B[31m404\x1B[39m":"\x1B[1m200\x1B[22m"} ${l.slice(i.length)}`);if(t)return o.writ' \
120
+ 'eHead(404,{"Content-Type":"text/plain"}),void o.end("404 Not Found");o.writeHead(200,{"Content-T' \
121
+ 'ype":n(l)}),o.end(e)})}).listen({host:"$HOST",port:+"$PORT"},()=>{console.log("HTTP server liste' \
122
+ 'ning on http://$HOST:$PORT")});'
123
+ private_constant :OPT_NPM, :OPT_PNPM, :OPT_YARN, :OPT_BERRY, :OPT_TSC, :PASS_NODE, :APP_SERVE
110
124
 
111
125
  class << self
112
126
  def tasks
@@ -160,6 +174,7 @@ module Squared
160
174
  end
161
175
  @dependname = 'package.json'
162
176
  dependfile_set [@dependname]
177
+ serve_set kwargs[:serve]
163
178
  @tsfile = basepath! ts
164
179
  @pm = { __: kwargs[:init] }
165
180
  end
@@ -1148,6 +1163,30 @@ module Squared
1148
1163
  end
1149
1164
  end
1150
1165
 
1166
+ def serve(root, opts = [], bind: nil, port: 3000, **)
1167
+ var = {}
1168
+ if File.extname(root) =~ /[cm]?js/
1169
+ var['HOST'] = bind if bind
1170
+ var['PORT'] = port.to_s if port
1171
+ target = root
1172
+ root = File.dirname(target)
1173
+ else
1174
+ require 'tempfile'
1175
+ app = APP_SERVE.dup
1176
+ app.gsub!('$ROOT', root)
1177
+ app.gsub!('$HOST', bind || 'localhost')
1178
+ app.gsub!('$PORT', port.to_s)
1179
+ file = Tempfile.new([name, '.cjs'])
1180
+ file.write(app)
1181
+ file.close
1182
+ trap 'INT' do
1183
+ file.unlink
1184
+ end
1185
+ target = file.path
1186
+ end
1187
+ shell(var, session('node', shell_quote(target), *opts.map { |s| fill_option(s) }).done, chdir: root)
1188
+ end
1189
+
1151
1190
  def depend?
1152
1191
  @depend != false && (!@depend.nil? || outdated?)
1153
1192
  end
@@ -1331,7 +1370,7 @@ module Squared
1331
1370
 
1332
1371
  def remove_modules(prefix = dependbin)
1333
1372
  modules = basepath 'node_modules'
1334
- return false unless modules.directory? && (option('y', prefix: prefix) || confirm_basic('Remove?', modules))
1373
+ return false unless modules.directory? && confirm_basic('Remove?', modules, prefix: prefix)
1335
1374
 
1336
1375
  modules.rmtree
1337
1376
  rescue Timeout::Error => e
@@ -1438,6 +1477,10 @@ module Squared
1438
1477
  ret
1439
1478
  end
1440
1479
 
1480
+ def serve?
1481
+ @serve != false
1482
+ end
1483
+
1441
1484
  def nolockfile?(prefix = dependbin)
1442
1485
  option('package-lock', 'lockfile', prefix: prefix, equals: '0') || !option('no-lockfile', prefix: prefix).nil?
1443
1486
  end
@@ -129,6 +129,7 @@ module Squared
129
129
  initialize_env(**kwargs)
130
130
  end
131
131
  dependfile_set DEP_PYTHON
132
+ serve_set kwargs[:serve]
132
133
  editable_set editable
133
134
  venv_set kwargs[:venv]
134
135
  end
@@ -272,7 +273,7 @@ module Squared
272
273
  if args.empty?
273
274
  args = readline('Enter command', force: true).split(' ', 2)
274
275
  elsif args.size == 1 && option('interactive', notequals: '0', prefix: ref)
275
- args << readline('Enter arguments', force: false)
276
+ args << readline('Enter arguments', force: false) unless args.first.include?(' ')
276
277
  end
277
278
  venv_init
278
279
  run args.join(' ')
@@ -804,6 +805,10 @@ module Squared
804
805
  .yield_self { |val| ret || val }
805
806
  end
806
807
 
808
+ def serve(root, opts = [], bind: 'localhost', port: nil, **)
809
+ shell(python_session('-m http.server', port, basic_option('b', bind), opts: opts).first.done, chdir: root)
810
+ end
811
+
807
812
  def project
808
813
  return @project unless @project.frozen?
809
814
 
@@ -855,6 +860,10 @@ module Squared
855
860
  !venv.nil?
856
861
  end
857
862
 
863
+ def serve?
864
+ @serve != false
865
+ end
866
+
858
867
  private
859
868
 
860
869
  def pip_session(*cmd)
@@ -213,6 +213,7 @@ module Squared
213
213
  initialize_env(**kwargs)
214
214
  end
215
215
  dependfile_set GEMFILE
216
+ serve_set kwargs[:serve]
216
217
  gemfile_set kwargs[:gemspec]
217
218
  @autodetect = autodetect
218
219
  @steepfile = basepath! steep if steep
@@ -458,9 +459,10 @@ module Squared
458
459
  bundle(flag, opts: args.to_a, banner: flag == :exec ? verbose? : true)
459
460
  end
460
461
  when :reinstall
461
- format_desc action, flag, 'f/orce?,opts*'
462
+ format_desc action, flag, 'f/orce?,l/ocal?,opts*'
462
463
  task flag do |_, args|
463
464
  opts = args.to_a
465
+ opts << 'local' if has_value!(opts, 'l')
464
466
  opts << 'redownload' if has_value!(opts, 'f', 'force')
465
467
  if (lock = basepath!('Gemfile.lock'))
466
468
  config = basepath '.bundle', 'config'
@@ -1167,6 +1169,7 @@ module Squared
1167
1169
  when :dependency, :environment, :list, :search, :specification, :which
1168
1170
  op.concat(args)
1169
1171
  end
1172
+ ia = op.remove(':')
1170
1173
  op.each do |opt|
1171
1174
  if gems && !opt.start_with?('-') && !opt.match?(GEMNAME)
1172
1175
  op.errors << opt
@@ -1228,7 +1231,7 @@ module Squared
1228
1231
  end
1229
1232
  when :install, :uninstall, :pristine
1230
1233
  if flag == :install
1231
- post = if op.remove(':')
1234
+ post = if ia
1232
1235
  op.concat(args)
1233
1236
  readline('Enter command [args]', force: true)
1234
1237
  elsif op.empty?
@@ -1237,6 +1240,25 @@ module Squared
1237
1240
  elsif !args.empty?
1238
1241
  args.join(' ')
1239
1242
  end
1243
+ elsif ia
1244
+ name = op.shift || args.shift || (s = readline('Enter gem name', force: true))
1245
+ list = []
1246
+ pwd_set do
1247
+ pat = /^#{name}\s+\((.+)\)$/
1248
+ IO.popen(gem_output("list -a #{shell_quote(name)}").to_s).each do |val|
1249
+ next unless val =~ pat
1250
+
1251
+ split_escape($1).each { |val| list << val unless val.start_with?('default:') }
1252
+ break
1253
+ end
1254
+ end
1255
+ ver = choice_index('Select version', list, force: s.nil?) unless list.empty?
1256
+ if ver
1257
+ op << '--force'
1258
+ op.unshift("#{name}@#{ver}")
1259
+ else
1260
+ op.unshift(name)
1261
+ end
1240
1262
  end
1241
1263
  raise_error ArgumentError, 'missing gem name', hint: flag if op.empty?
1242
1264
  if op.arg?('all')
@@ -1246,7 +1268,7 @@ module Squared
1246
1268
  else
1247
1269
  op.clear
1248
1270
  end
1249
- elsif (n = op.index { |val| val.match?(/(\A|[a-z])@\d/) })
1271
+ elsif (n = op.index { |val| val.match?(/(\A|[\w.-])@\d/) })
1250
1272
  name = op.remove_at(n)
1251
1273
  n = name.index('@')
1252
1274
  pre, ver = if n == 0
@@ -1254,7 +1276,7 @@ module Squared
1254
1276
  else
1255
1277
  [name[0, n], name[n.succ..-1]]
1256
1278
  end
1257
- op.adjoin(pre, basic_option('version', ver))
1279
+ op.adjoin(pre, quote_option('version', ver))
1258
1280
  .clear
1259
1281
  end
1260
1282
  if flag == :install
@@ -1540,6 +1562,18 @@ module Squared
1540
1562
  run(sync: sync, banner: banner, exception: kwargs.fetch(:exception, exception?), from: :rubocop)
1541
1563
  end
1542
1564
 
1565
+ def serve(root, *, bind: nil, port: nil, **kwargs)
1566
+ require 'webrick'
1567
+ config = kwargs.merge({ DocumentRoot: root })
1568
+ config[:BindAddress] = bind if bind
1569
+ config[:Port] = port if port
1570
+ server = WEBrick::HTTPServer.new(config)
1571
+ trap 'INT' do
1572
+ server.shutdown
1573
+ end
1574
+ server.start
1575
+ end
1576
+
1543
1577
  def gemspec
1544
1578
  @gemspec = !gemfile.nil? && Gem::Specification.load(gemfile.to_s) rescue false if @gemspec.nil?
1545
1579
  @gemspec || nil
@@ -1873,6 +1907,13 @@ module Squared
1873
1907
  semgte?(ver.to_s, min.to_s) && (max == Float::INFINITY || !semgte?(ver.to_s, max.to_s))
1874
1908
  end
1875
1909
  end
1910
+
1911
+ def serve?
1912
+ !Gem::Specification.find_by_name('webrick').nil?
1913
+ rescue Gem::MissingSpecError => e
1914
+ log.warn e if @serve
1915
+ false
1916
+ end
1876
1917
  end
1877
1918
 
1878
1919
  Application.implement Ruby
@@ -221,6 +221,8 @@ module Squared
221
221
  si = []
222
222
  bl = []
223
223
  ml = []
224
+ sw = {}
225
+ se = {}
224
226
  list.flat_map do |val|
225
227
  x, y = val.split('|', 2)
226
228
  if y
@@ -233,6 +235,11 @@ module Squared
233
235
  end
234
236
  end
235
237
  .each do |val|
238
+ if val.size > 1 && val =~ /^\{([^},]*)(?:,([^}]+))?\}(.+)$/
239
+ op1 = $1 unless $1.empty?
240
+ op2 = $2
241
+ val = $3
242
+ end
236
243
  if (n = val.index('='))
237
244
  flag = val[0, n]
238
245
  case val[n.succ]
@@ -265,9 +272,12 @@ module Squared
265
272
  end
266
273
  m << flag if val[n + 2] == 'm'
267
274
  bare << flag if val.end_with?('?')
275
+ val = flag
268
276
  else
269
277
  bare << val
270
278
  end
279
+ sw[val] = op1 if op1
280
+ se[val] = op2 if op2
271
281
  end
272
282
  no = (no || []).map { |val| (n = val.index('=')) ? val[0, n] : val }
273
283
  bare.concat(no)
@@ -316,30 +326,34 @@ module Squared
316
326
  if single&.match?(opt)
317
327
  add "-#{opt}"
318
328
  elsif bare.include?(opt)
319
- add(opt.size == 1 ? "-#{opt}" : "--#{opt}")
320
- elsif opt.start_with?(/no[-_]/) && no.include?(name = opt[3..-1])
321
- add "--no-#{name}"
329
+ if sw[opt]
330
+ add "#{sw[opt]}#{opt}"
331
+ else
332
+ add(opt.size == 1 ? "-#{opt}" : "--#{opt}")
333
+ end
334
+ elsif opt.start_with?(/no[-_]/) && no.include?(s = opt[3..-1])
335
+ add "--no-#{s}"
322
336
  else
323
337
  if opt =~ OPT_VALUE
324
338
  key = $1
325
339
  val = $2
326
340
  merge = m.include?(key)
327
- if e.include?(key)
328
- add shell_option(key, val, merge: merge, sep: sep)
341
+ switch = sw[key]
342
+ kwargs = { sep: se[key] || sep, merge: merge, switch: switch }
343
+ if e.include?(key) || (bl.include?(key) && %w[true false].include?(val))
344
+ add shell_option(key, val, **kwargs)
329
345
  elsif q.include?(key)
330
- add quote_option(key, val, double: qq.include?(key), merge: merge, sep: sep)
346
+ add quote_option(key, val, double: qq.include?(key), **kwargs)
331
347
  elsif p.include?(key)
332
348
  if val.match?(/\A(["']).+\1\z/)
333
- add shell_option(key, val, escape: false, merge: merge, sep: sep)
349
+ add shell_option(key, val, escape: false, **kwargs)
334
350
  elsif path
335
- add quote_option(key, path + val, merge: merge, sep: sep)
351
+ add quote_option(key, path + val, **kwargs)
336
352
  else
337
353
  push opt
338
354
  end
339
- elsif b.include?(key) || (bl.include?(key) && %w[true false].include?(val)) || numcheck.call(key, val)
340
- add basic_option(key, val, merge: merge, sep: sep)
341
- elsif merge
342
- add basic_option(key, val, merge: true, sep: sep)
355
+ elsif b.include?(key) || numcheck.call(key, val) || se[key] || merge || switch
356
+ add basic_option(key, val, **kwargs)
343
357
  else
344
358
  push opt
345
359
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: squared
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - An Pham