squared 0.9.0 → 0.9.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.
@@ -301,7 +301,7 @@ module Squared
301
301
  if footer
302
302
  unless sub.empty? && !right
303
303
  footer.map! do |s|
304
- s = s.rjust(n + 4) if right
304
+ s = s.rjust(n + ARG[:TAB]) if right
305
305
  sub.each { |h| sub_style!(s, **h) }
306
306
  s
307
307
  end
@@ -5,12 +5,30 @@ require 'timeout'
5
5
  module Squared
6
6
  module Common
7
7
  module Prompt
8
- def self.reline
9
- require 'reline'
10
- Reline
11
- rescue LoadError
12
- require 'readline'
13
- Readline
8
+ class << self
9
+ def reline
10
+ require 'reline'
11
+ Reline
12
+ rescue LoadError
13
+ require 'readline'
14
+ Readline
15
+ end
16
+
17
+ def multiline=(val)
18
+ raise NotImplementedError, 'multiline is available only with reline' unless multiline?
19
+
20
+ @multiline = case val
21
+ when '', '0', 'false' then nil
22
+ else
23
+ val.split(',').map(&:strip)
24
+ end
25
+ end
26
+
27
+ def multiline?
28
+ reline.respond_to?(:readmultiline)
29
+ end
30
+
31
+ attr_reader :multiline
14
32
  end
15
33
 
16
34
  module_function
@@ -169,8 +187,8 @@ module Squared
169
187
  end
170
188
  end
171
189
 
172
- def readline(msg, history = false, force: nil, multiline: nil, &blk)
173
- multiline = nil unless Prompt.reline.respond_to?(:readmultiline)
190
+ def readline(msg, history = false, force: nil, multiline: Prompt.multiline, &blk)
191
+ multiline = nil unless Prompt.multiline?
174
192
  read = lambda do
175
193
  if !multiline
176
194
  Prompt.reline.readline(msg, history)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.9.0'
4
+ VERSION = '0.9.2'
5
5
  end
@@ -167,6 +167,11 @@ module Squared
167
167
  @series.ref_min = app[:series_ref_min] if app.key?(:series_ref_min)
168
168
  kwargs[:pattern].concat(app[:build_pattern]) if app.key?(:build_pattern)
169
169
  kwargs[:parallel].concat(app[:build_parallel]) if app.key?(:build_parallel)
170
+ kwargs[:parallel].reject! { |val| app[:build_parallel!].include?(val) } if app.key?(:build_parallel!)
171
+ app[:build_pattern!]&.each do |pat|
172
+ kwargs[:pattern].delete(pat)
173
+ kwargs[:parallel].reject! { |val| pat.match?(val) }
174
+ end
170
175
  self.closed = 'begin'
171
176
  pb = @stage.key?('project:begin')
172
177
  pe = @stage.key?('project:end')
@@ -607,10 +607,10 @@ module Squared
607
607
  @clean.each do |val|
608
608
  src = basepath(val = val.to_s)
609
609
  if src.directory? && val.match?(%r{[\\/]\z})
610
- log&.warn "rm -rf #{src}"
610
+ log&.info "rm -rf #{src}"
611
611
  rm_rf(src, verbose: !silent?)
612
612
  else
613
- log&.warn "rm #{src}"
613
+ log&.info "rm #{src}"
614
614
  (val.include?('*') ? Dir[src] : [src]).each do |file|
615
615
  next unless File.file?(file)
616
616
 
@@ -864,7 +864,7 @@ module Squared
864
864
  instance_variable_set :"@#{key}", [blk]
865
865
  end
866
866
  else
867
- log&.warn "series: @#{key}".subhint('invalid')
867
+ log&.debug "series: @#{key}".subhint('invalid')
868
868
  end
869
869
  self
870
870
  end
@@ -876,7 +876,7 @@ module Squared
876
876
  return
877
877
  end
878
878
  cmd = cmd.target if cmd.is_a?(OptionPartition)
879
- if interactive && sync && !(@session && option('y'))
879
+ if interactive && sync && !(@session && option('y', 'yes'))
880
880
  print_item(series: true)
881
881
  msg, y, h = case interactive
882
882
  when Array
@@ -1019,7 +1019,7 @@ module Squared
1019
1019
  instance_variable_set(:"@#{key}", val)
1020
1020
  end
1021
1021
  else
1022
- log&.warn "variable_set: @#{key}".subhint('private')
1022
+ log&.debug "variable_set: @#{key}".subhint('private')
1023
1023
  end
1024
1024
  self
1025
1025
  end
@@ -1701,7 +1701,7 @@ module Squared
1701
1701
  ret = case key.downcase
1702
1702
  when 'force'
1703
1703
  getarg.call(:FORCE)
1704
- when 'y'
1704
+ when 'y', 'yes'
1705
1705
  getarg.call(:YES)
1706
1706
  end
1707
1707
  return yield(ret) if !ret.nil? && block_given?
@@ -2161,7 +2161,7 @@ module Squared
2161
2161
  end
2162
2162
 
2163
2163
  def confirm_basic(msg, hint, accept = 'Y', style: :inline, target: @session, prefix: target&.first, **kwargs)
2164
- return true if option('y', prefix: prefix)
2164
+ return true if option('y', 'yes', prefix: prefix)
2165
2165
 
2166
2166
  confirm("#{msg} [#{sub_style(hint.to_s, style.is_a?(Symbol) ? theme[style] : style)}]", accept, **kwargs)
2167
2167
  end
@@ -2726,10 +2726,12 @@ module Squared
2726
2726
  end
2727
2727
 
2728
2728
  def checkdir?(val)
2729
- return true if val.directory? && !val.empty?
2730
-
2731
- log&.warn "directory \"#{val}\"".subhint(val.directory? ? 'empty' : 'missing')
2732
- false
2729
+ if val.directory? && !val.empty?
2730
+ true
2731
+ else
2732
+ log&.warn "directory \"#{val}\"".subhint(val.directory? ? 'empty' : 'missing')
2733
+ false
2734
+ end
2733
2735
  end
2734
2736
 
2735
2737
  def printfirst?
@@ -422,7 +422,7 @@ module Squared
422
422
  def clean(*, sync: invoked_sync?('clean'), **)
423
423
  if runnable?(@clean)
424
424
  super
425
- elsif sync || option('y', prefix: 'docker')
425
+ elsif sync || option('y', 'yes', prefix: 'docker')
426
426
  image :rm
427
427
  end
428
428
  end
@@ -731,7 +731,7 @@ module Squared
731
731
  return
732
732
  end
733
733
  op << id
734
- if option('y')
734
+ if option('y', 'yes')
735
735
  exception = false
736
736
  banner = false
737
737
  end
@@ -966,7 +966,7 @@ module Squared
966
966
  pwd_set(from: from) do
967
967
  index = 1
968
968
  all = option('all', prefix: 'docker')
969
- y = from == :'image:rm' && option('y', prefix: 'docker')
969
+ y = from == :'image:rm' && option('y', 'yes', prefix: 'docker')
970
970
  filter = env('DOCKER_FILTER', filter).to_s
971
971
  pat = if OptionPartition.pattern?(filter)
972
972
  Regexp.new(filter)
@@ -1013,7 +1013,7 @@ module Squared
1013
1013
  end
1014
1014
  pad = cols.map(&:size).max
1015
1015
  cols.each { |key| puts "#{g + f} #{key.rjust(pad)}: #{Array(data[key]).join(', ')}" }
1016
- w = 9 + flag.to_s.size + 4 + ee.size
1016
+ w = 9 + flag.to_s.size + ARG[:TAB] + ee.size
1017
1017
  puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), theme[:inline])
1018
1018
  index += 1
1019
1019
  next unless confirm("#{h + b}?", no ? 'N' : 'Y')
@@ -363,7 +363,7 @@ module Squared
363
363
  subtasks({
364
364
  'branch' => %i[create track delete move copy list all current].freeze,
365
365
  'checkout' => %i[commit branch track detach path].freeze,
366
- 'commit' => %i[head add all amend amend-orig fixup].freeze,
366
+ 'commit' => %i[head add all pathspec amend amend-orig fixup].freeze,
367
367
  'clone' => %i[branch tag].freeze,
368
368
  'diff' => %i[head branch files view between contain].freeze,
369
369
  'range-diff' => %i[view between contain].freeze,
@@ -530,7 +530,7 @@ module Squared
530
530
  if flag == :add
531
531
  opts = param_guard(action, flag, args: args.to_a)
532
532
  elsif (refs = args.to_a).empty?
533
- refs = readline('Enter file patterns', force: true).shellsplit
533
+ refs = readline('Enter glob patterns', force: true).shellsplit
534
534
  end
535
535
  end
536
536
  commit(flag, opts, refs: refs, ref: ref, squash: squash, pick: pick)
@@ -1773,7 +1773,7 @@ module Squared
1773
1773
  pathspec = false if pick == 'reword'
1774
1774
  end
1775
1775
  if pathspec.nil?
1776
- pathspec = if (flag == :fixup || amend) && refs.size == 1 && refs.first == '*'
1776
+ pathspec = if (flag == :pathspec || flag == :fixup || amend) && refs.size == 1 && refs.first == '*'
1777
1777
  '--all'
1778
1778
  elsif (refs = projectmap(refs)).empty?
1779
1779
  raise 'no qualified pathspec'
@@ -1847,7 +1847,7 @@ module Squared
1847
1847
  elsif stdout?
1848
1848
  puts log_message('nothing to commit', subject: name, hint: flag)
1849
1849
  end
1850
- exit 1
1850
+ exit 1 unless pass.nil?
1851
1851
  end
1852
1852
  end
1853
1853
 
@@ -11,7 +11,7 @@ module Squared
11
11
  install_a: %w[audit=! bin-links=! engine-strict=!? foreground-scripts=!? fund=! ignore-scripts=!?
12
12
  install-links=!? package-lock=! strict-peer-deps=!? include=b install-strategy=b omit=b].freeze,
13
13
  install_b: %w[no-save B|save-bundle D|save-dev O|save-optional save-peer P|save-prod g|global=!?
14
- S|save=!?].freeze,
14
+ min-release-age=i min-release-age-exclude=q S|save=!?].freeze,
15
15
  install_c: %w[allow-directory=b? allow-file=b? allow-git=b? allow-remote=b?].freeze,
16
16
  run: %w[foreground-scripts=!? if-present=!? ignore-scripts=!? script-shell=p].freeze,
17
17
  exec: %w[c|call=q package=b].freeze,
@@ -230,7 +230,7 @@ module Squared
230
230
  elsif exception?
231
231
  indexerror n, list
232
232
  else
233
- log.warn "run script #{n} of #{list.size}".subhint('out of range')
233
+ log.debug "run script #{n} of #{list.size}".subhint('out of range')
234
234
  next
235
235
  end
236
236
  else
@@ -330,26 +330,39 @@ module Squared
330
330
  when 'package'
331
331
  case flag
332
332
  when :approve, :deny
333
- format_desc action, flag, 'name*|:,opts*'
333
+ format_desc(action, flag, 'opts*', before: flag == :approve ? 'name*|:' : 'name*|:|::')
334
334
  task flag do |_, args|
335
335
  args = args.to_a
336
- if choice!(args)
336
+ if choice!(args) || (flag == :deny && (all = choice!(args, char: '::')))
337
+ list = []
338
+ pad = 0
339
+ size = ->(s) { pad = [pad, s.size].max }
337
340
  pwd_set do
338
- pad = 0
339
- list = IO.popen(npm_output('approve-scripts --allow-scripts-pending').to_s)
340
- .each_with_object([]) do |line, out|
341
- next unless line =~ /^\s+([a-z\d@][^@]*)@(\S+)/
342
-
343
- pad = [pad, $1.size].max
344
- out << [$1, $2]
345
- end
346
- .map { |name, ver| name.ljust(pad + 4) + ver }
347
- if list.empty?
348
- puts log_message('no packages to review', subject: name, hint: "#{flag}-scripts")
349
- exit
341
+ json = JSON.parse(`#{npm_output('install-scripts ls --json')}`)
342
+ json&.fetch('allowScripts', [])&.each do |data|
343
+ line = data['changes']&.find { |item| item['change'] == 'pending' }
344
+ next unless line && line['key'] =~ /^(.+)@([^@]+)$/
345
+
346
+ size.call($1)
347
+ list << [$1, $2]
348
+ end
349
+ if all
350
+ current = read_package 'allowScripts'
351
+ current&.each_pair do |key, val|
352
+ next unless val
353
+
354
+ md = key.match(/^(.+?)(?:@([^@]+))?$/)
355
+ size.call(md[1])
356
+ list << [md[1], sub_style(md[2] || 'any', theme[:inline])]
357
+ end
350
358
  end
351
- args.concat(choice_index('Select packages', list, multiple: true, column: /^(\S+)/))
352
359
  end
360
+ if list.empty?
361
+ puts log_message('no packages to review', subject: name, hint: "#{flag}-scripts")
362
+ exit
363
+ end
364
+ list.map! { |name, ver| name.ljust(pad + ARG[:TAB]) + ver }
365
+ args.concat(choice_index('Select packages', list, multiple: true, column: /^(\S+)/))
353
366
  end
354
367
  package flag, args
355
368
  end
@@ -885,9 +898,9 @@ module Squared
885
898
  end
886
899
  print_item banner unless sync
887
900
  if !found.empty?
888
- col1 = width.call(found, 0) + 4
889
- col2 = width.call(found, 1) + 4
890
- col3 = pad.call(found, found.size).size + 2 + col1 + col2 + width.call(found, 2)
901
+ col1 = width.call(found, 0) + ARG[:TAB]
902
+ col2 = width.call(found, 1) + ARG[:TAB]
903
+ col3 = pad.call(found, found.size).size + (ARG[:TAB] / 2) + col1 + col2 + width.call(found, 2)
891
904
  packages = []
892
905
  pat = ->(a) { /("#{Regexp.escape(a[0])}"\s*:\s*)"([~^])#{'?' if a[4]}#{Regexp.escape(a[1])}"/ }
893
906
  edit = lambda do |a, pkg, mod|
@@ -983,9 +996,9 @@ module Squared
983
996
  end
984
997
  end
985
998
  elsif !avail.empty?
986
- col1 = width.call(avail, 0) + 4
999
+ col1 = width.call(avail, 0) + ARG[:TAB]
987
1000
  col2 = width.call(avail, 1)
988
- col3 = width.call(avail, 2) + 4
1001
+ col3 = width.call(avail, 2) + ARG[:TAB]
989
1002
  avail.each_with_index do |item, i|
990
1003
  a, b, c, d = item
991
1004
  a = a.ljust(col1)
@@ -1381,7 +1394,7 @@ module Squared
1381
1394
  op.exist?(add: true)
1382
1395
  end
1383
1396
  end
1384
- op.append(flags, escape: kwargs.fetch(:escape, false), quote: kwargs.fetch(:quote, false))
1397
+ op.append(flags, context: kwargs)
1385
1398
  .clear
1386
1399
  print_run(op, banner, **kwargs)
1387
1400
  start = time_epoch if kwargs.fetch(:verbose, verbose? && !stdin?)
@@ -7,16 +7,18 @@ module Squared
7
7
  DEP_PYTHON = %w[poetry.lock pdm.lock pylock.toml setup.cfg pyproject.toml setup.py requirements.txt].freeze
8
8
  DIR_PYTHON = (DEP_PYTHON + %w[meson.build README.rst]).freeze
9
9
  OPT_PYTHON = {
10
- common: %w[b=+ B d E h i I O P q=+ s S u v=+ V=+ x c=q m=b W=b X=q check-hash-based-pycs=b].freeze,
10
+ common: %w[b=+ B d E h i I O P q=+ s S u v=+ V=+ x c=q m=b W=b X=q
11
+ check-hash-based-pycs={always,default,never}].freeze,
11
12
  build: %w[C=bm n|no-isolation s|sdist x|skip-dependency-check v|verbose w|wheel config-json=q config-setting=q
12
13
  installer=b o|outdir=p].freeze,
13
14
  venv: %w[clear copies symlinks system-site-packages upgrade upgrade-deps without-scm-ignore-files without-pip
14
15
  prompt=q].freeze,
15
16
  virtualenv: %w[always-copy clear copies download never-download no-download no-periodic-update no-pip no-seed
16
- no-setuptools no-vcs-ignore q|quiet read-only-app-data reset-app-data symlink-app-data symlinks
17
- system-site-packages with-traceback without-pip upgrade-embed-wheels v|verbose=+ activators=q
18
- app-data=p creator=b discovery=b extra-search-dir=p pip=b prompt=q p|python=q seeder=b
19
- setuptools=b try-first-with=q].freeze
17
+ no-setuptools no-vcs-ignore no-venv-redirect q|quiet read-only-app-data reset-app-data
18
+ symlink-app-data symlinks system-site-packages with-traceback without-pip upgrade-embed-wheels
19
+ venv-redirect v|verbose=+ activators={bash,batch,cshell,fish,nushell,powershell,python,xonsh}
20
+ app-data=p creator=b discovery={builtin} extra-search-dir=p pip=b prompt=q p|python=q
21
+ seeder={app-data,pip} setuptools=b try-first-with=q].freeze
20
22
  }.freeze
21
23
  OPT_PIP = {
22
24
  common: %w[debug disable-pip-version-check isolated no-cache-dir no-color no-input no-proxy-env
@@ -49,6 +51,13 @@ module Squared
49
51
  uninstall: %w[break-system-packages y|yes r|requirement=p root-user-action=b].freeze,
50
52
  wheel: %w[no-verify w|wheel-dir=p].freeze
51
53
  }.freeze
54
+ OPT_PY = {
55
+ common: %w[q|quiet=+ v|verbose=+ y|yes config=p].freeze,
56
+ exec: %w[3=/^\.\d+(\.\d+)?$/m? V=/^:./m?].freeze,
57
+ install: %w[by-id configure dry-run f|force refresh u|update d|download=p s|source=q t|target=p].freeze,
58
+ list: %w[1|one online only-managed format={table,json,jsonl,csv,exe,prefix,url,formats} s|source=q].freeze,
59
+ uninstall: %w[by-id cleanup purge].freeze
60
+ }.freeze
52
61
  OPT_POETRY = {
53
62
  common: %w[ansi no-ansi no-cache n|no-interaction no-plugins q|quiet v|verbose=+ P|project=p].freeze,
54
63
  install: %w[all-extras all-groups compile dry-run no-directory no-root only-root E|extras=q only=q with=q
@@ -99,7 +108,8 @@ module Squared
99
108
  install: %w[C config-settings c constraint extra-index-url no-binary only-binary platform
100
109
  r requirement].freeze,
101
110
  list: %w[exclude extra-index-url no-binary only-binary].freeze
102
- }.freeze
111
+ }.freeze,
112
+ py: %w[q v quiet verbose].freeze
103
113
  }.freeze
104
114
  private_constant :DEP_PYTHON, :DIR_PYTHON, :OPT_PYTHON, :OPT_PIP, :OPT_POETRY, :OPT_PDM, :OPT_HATCH, :OPT_TWINE,
105
115
  :PASS_PYTHON
@@ -149,6 +159,7 @@ module Squared
149
159
  'outdated' => %i[major minor patch].freeze,
150
160
  'build' => %i[poetry pdm hatch meson python].freeze,
151
161
  'publish' => %i[twine poetry pdm hatch].freeze,
162
+ 'py' => %i[exec install list uninstall].freeze,
152
163
  'run' => nil,
153
164
  'exec' => nil
154
165
  })
@@ -210,7 +221,7 @@ module Squared
210
221
  indexerror n, list
211
222
  else
212
223
  found |= 2
213
- log.warn "run script #{n} of #{list.size}".subhint('out of range')
224
+ log.debug "run script #{n} of #{list.size}".subhint('out of range')
214
225
  end
215
226
  else
216
227
  case (i > 1 && poetry? ? 0 : i)
@@ -224,7 +235,7 @@ module Squared
224
235
  raise message('script', val) if exception?
225
236
 
226
237
  found |= 2
227
- log.warn "run script \"#{val}\"".subhint('not indexed')
238
+ log.debug "run script \"#{val}\"".subhint('not indexed')
228
239
  end
229
240
  end
230
241
  end
@@ -242,7 +253,7 @@ module Squared
242
253
  task action do |_, args|
243
254
  args = args.to_a
244
255
  cmd = if (i = args.delete(':')) && !windows?
245
- readline('Enter script', force: true, multiline: %w[## ;])
256
+ readline('Enter script', force: true, multiline: Prompt.multiline || %w[## ;])
246
257
  elsif i || args.empty?
247
258
  readline('Enter command', force: true)
248
259
  else
@@ -281,7 +292,7 @@ module Squared
281
292
  args = args.to_a
282
293
  if args.empty?
283
294
  args = readline('Enter command', force: true).split(' ', 2)
284
- elsif args.size == 1 && option('interactive', notequals: '0', prefix: ref)
295
+ elsif args.size == 1 && option('interactive', notequals: '0', prefix: 'python')
285
296
  args << readline('Enter arguments', force: false) unless args.first.include?(' ')
286
297
  end
287
298
  venv_init
@@ -351,18 +362,18 @@ module Squared
351
362
  case flag
352
363
  when :upgrade
353
364
  task flag, [:strategy] do |_, args|
354
- install flag, (case args.strategy
355
- when 'eager'
356
- 'eager'
357
- when /^only-if|needed$/
358
- 'only-if-needed'
359
- end.then do |val|
360
- if val
361
- args.extras << "upgrade-strategy=#{val}"
362
- else
363
- args.to_a
364
- end
365
- end)
365
+ val = case args.strategy
366
+ when 'eager'
367
+ 'eager'
368
+ when /^only-if/, /needed$/
369
+ 'only-if-needed'
370
+ end
371
+ args = if val
372
+ args.extras << "upgrade-strategy=#{val}"
373
+ else
374
+ args.to_a
375
+ end
376
+ install flag, args
366
377
  end
367
378
  when :requirement, :target
368
379
  task flag, [:path] do |_, args|
@@ -419,6 +430,26 @@ module Squared
419
430
  end)
420
431
  end
421
432
  break if be
433
+ when 'py'
434
+ break unless windows?
435
+
436
+ case flag
437
+ when :exec
438
+ format_desc action, flag, 'opts*'
439
+ task flag do |_, args|
440
+ opts = args.to_a
441
+ if (n = opts.index { |val| val.start_with?('3.') }) && (opts[n] =~ SEM_VER)
442
+ opts[n] = $3.to_i >= 5 ? "3=.#{$3}.#{$5 || '0'}" : '3'
443
+ end
444
+ command = ENV['PY_EXEC'] || readline('Enter command', force: true)
445
+ py(flag, command, opts: opts)
446
+ end
447
+ else
448
+ format_desc(action, flag, 'opts*', after: flag == :list ? 'filter*' : 'tag*')
449
+ task flag do |_, args|
450
+ py(flag, opts: args.to_a, banner: true)
451
+ end
452
+ end
422
453
  end
423
454
  end
424
455
  end
@@ -751,10 +782,17 @@ module Squared
751
782
  run(from: symjoin(flag, 'publish'), interactive: ['Publish', 'N', project])
752
783
  end
753
784
 
754
- def python(*args, sync: true, banner: verbose?, with: nil, pass: PASS_PYTHON[:python], **kwargs)
755
- op = OptionPartition.new(session_opts(with, args: args, kwargs: kwargs, pass: pass), OPT_PYTHON[:common],
756
- session('python', path: !venv?),
757
- project: self, strict: strict?, auto: %w[help V|version],
785
+ def python(*args, sync: true, banner: verbose?, with: nil, pass: PASS_PYTHON[:python], from: :python, **kwargs)
786
+ py = from == :py && windows?
787
+ list = OPT_PYTHON[:common]
788
+ if py
789
+ list += OPT_PY[:exec]
790
+ list.delete('V=+')
791
+ from = :'py:exec'
792
+ end
793
+ op = OptionPartition.new(session_opts(with, args: args, kwargs: kwargs, pass: pass), list,
794
+ py ? session('py', 'exec') : session('python', path: !venv?),
795
+ project: self, strict: strict?, auto: %w[help V|version], sep: ' ',
758
796
  multiple: [/^-c/], single: singleopt(:python), args: true, stdin: true)
759
797
  op.concat(args)
760
798
  if op.include?('-')
@@ -765,11 +803,17 @@ module Squared
765
803
  op.clear
766
804
  else
767
805
  op.exist?(add: true, first: true) unless op.arg?('m')
768
- op.append(escape: kwargs.fetch(:escape, false), quote: kwargs.fetch(:quote, false))
806
+ op.append(context: kwargs)
769
807
  end
770
808
  end
809
+ var = kwargs[:env]
810
+ if var == true
811
+ var = nil
812
+ elsif py && var.nil?
813
+ var = false
814
+ end
771
815
  print_run(op, banner, **kwargs) unless op.stdout
772
- run(sync: sync, banner: op.style?(banner), exception: exception?(**kwargs), from: :python)
816
+ run(op, var, sync: sync, banner: op.style?(banner), exception: exception?(**kwargs), from: from)
773
817
  end
774
818
 
775
819
  def pip(flag, *args, sync: true, banner: verbose?, with: nil, pass: nil, **kwargs)
@@ -855,6 +899,28 @@ module Squared
855
899
  .then { |val| ret || val }
856
900
  end
857
901
 
902
+ def py(flag, *args, sync: true, banner: verbose?, with: nil, pass: nil, **kwargs)
903
+ flag = flag.to_sym
904
+ if flag == :exec
905
+ pass = PASS_PYTHON[:python] if pass.nil?
906
+ python(*args, sync: sync, banner: banner, with: with, pass: pass, from: :py, **kwargs)
907
+ else
908
+ pass = PASS_PYTHON[:py] if pass.nil?
909
+ opts = session_opts(with, args: args, kwargs: kwargs, pass: pass)
910
+ list = OPT_PY[:common] + OPT_PY[flag]
911
+ op = OptionPartition.new(opts, list, session('py', flag), project: self, strict: strict?,
912
+ single: singleopt(:py))
913
+ case flag
914
+ when :install, :uninstall
915
+ raise ArgumentError, message('no tags listed', hint: flag) if op.empty?
916
+ end
917
+ op << '-y' if option('y', 'yes')
918
+ op.append
919
+ print_run(op, banner, **kwargs)
920
+ run(sync: sync, banner: banner, exception: exception?(**kwargs), from: symjoin('py', flag))
921
+ end
922
+ end
923
+
858
924
  def serve(root, opts = [], bind: 'localhost', port: nil, **)
859
925
  shell(python_session('-m http.server', port, basic_option('b', bind), opts: opts).first.done, chdir: root)
860
926
  end
@@ -886,7 +952,7 @@ module Squared
886
952
  @dependindex = index
887
953
  @dependfile = val
888
954
  else
889
- log.warn "variable_set: @dependfile=#{val}".subhint('not supported')
955
+ log.debug "variable_set: @dependfile=#{val}".subhint('not supported')
890
956
  end
891
957
  end
892
958
  when :venv
@@ -956,7 +1022,8 @@ module Squared
956
1022
  pre = preopts(quiet: quiet)
957
1023
  return session(name, *pre, *cmd, path: !venv?) unless opts
958
1024
 
959
- op = OptionPartition.new(opts, common, project: self, strict: strict?, single: singleopt(name.to_sym))
1025
+ op = OptionPartition.new(opts, common, project: self, strict: strict?, sep: name == 'python' ? ' ' : '=',
1026
+ single: singleopt(name.to_sym))
960
1027
  [session(name, *pre, *op.to_a, *cmd, path: !venv?), op.extras]
961
1028
  end
962
1029
 
@@ -1131,6 +1198,8 @@ module Squared
1131
1198
  when :pdm, :poetry
1132
1199
  /\Av+\z/
1133
1200
  when :twine, :meson then nil
1201
+ when :py
1202
+ /\A(vv|qq)\z/
1134
1203
  else
1135
1204
  /\A(v+|q+)\z/
1136
1205
  end
@@ -1235,6 +1304,7 @@ module Squared
1235
1304
  status = true
1236
1305
  :virtualenv
1237
1306
  else
1307
+ status = !banner
1238
1308
  :venv
1239
1309
  end
1240
1310
  cmd, opts = python_session('-m', flag, opts: opts)
@@ -1242,34 +1312,31 @@ module Squared
1242
1312
  op.append(dir, delim: true)
1243
1313
  .clear(pass: false)
1244
1314
  status ||= op.arg?(/\A-v+\z/)
1245
- ret = run(op, env, exception: true, banner: banner)
1246
- args = []
1247
- requires = read_pyproject 'build-system', 'requires'
1248
- args.concat(requires) if requires.is_a?(Array)
1249
- if poetry?
1250
- requires = read_pyproject 'tool.poetry', 'requires-poetry'
1251
- args << "poetry#{requires}"
1252
- elsif setuptools? && !virtualenv?
1253
- args << 'setuptools' << 'wheel'
1254
- end
1255
- pip(:install, *args, banner: false) unless args.empty?
1256
- success?(ret, banner, !status) do |out|
1257
- if out && dir.directory?
1258
- if poetry?
1259
- dir += (windows? ? 'Scripts/python.exe' : 'bin/python')
1260
- cmd = poetry_session 'env use'
1261
- elsif pdm?
1262
- cmd = pdm_session 'use -f'
1263
- else
1264
- puts "Success: #{dir}"
1265
- return
1266
- end
1267
- cmd << shell_quote(dir)
1268
- print_run cmd, silent?
1269
- run(banner: false)
1315
+ if run(op, env, exception: true, banner: banner) && dir.directory?
1316
+ args = []
1317
+ requires = read_pyproject 'build-system', 'requires'
1318
+ args.concat(requires) if requires.is_a?(Array)
1319
+ if poetry?
1320
+ requires = read_pyproject 'tool.poetry', 'requires-poetry'
1321
+ args << "poetry#{requires}"
1322
+ elsif setuptools? && !virtualenv?
1323
+ args << 'setuptools' << 'wheel'
1324
+ end
1325
+ pip(:install, *args, banner: false) unless args.empty?
1326
+ if poetry?
1327
+ dir += (windows? ? 'Scripts/python.exe' : 'bin/python')
1328
+ poetry_session 'env use'
1329
+ elsif pdm?
1330
+ pdm_session 'use -f'
1270
1331
  else
1271
- puts 'Failed'
1332
+ puts "Success: #{dir}" unless status
1333
+ return
1272
1334
  end
1335
+ cmd << shell_quote(dir)
1336
+ print_run cmd, silent?
1337
+ run(banner: false)
1338
+ elsif !status
1339
+ puts 'Failed'
1273
1340
  end
1274
1341
  end
1275
1342