squared 0.5.12 → 0.6.0

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.
@@ -14,18 +14,33 @@ module Squared
14
14
  prompt=q].freeze
15
15
  }.freeze
16
16
  OPT_PIP = {
17
- common: %w[debug disable-pip-version-check isolated no-cache-dir no-color no-input no-python-version-warning
18
- q|quiet require-virtualenv v|verbose cache-dir=p cert=p client-cert=p exists-action=b log=p proxy=q
19
- python=q retries=i timeout=i trusted-host=b use-deprecated=b use-feature=b].freeze,
20
- install: %w[break-system-packages check-build-dependencies compile dry-run force-reinstall I|ignore-installed
21
- ignore-requires-python no-build-isolation no-clean no-compile no-deps no-index no-warn-conflicts
22
- no-warn-script-location pre prefer-binary require-hashes U|upgrade use-pep517 user abi=b
23
- config-settings=q c|constraint=p e|editable=v extra-index-url=q f|find-links=q global-option=q
24
- implementation=b i|index-url=q no-binary=q only-binary=q platform=q prefix=p progress-bar=b
25
- python-version=q report=p r|requirement=p root=p root-user-action=b src=p t|target=p
26
- upgrade-strategy=b].freeze,
17
+ common: %w[debug disable-pip-version-check isolated no-cache-dir no-color no-input q|quiet require-virtualenv
18
+ v|verbose cache-dir=p cert=p client-cert=p exists-action=b keyring-provider=b log=p proxy=q
19
+ python=q resume-retries=i retries=i timeout=i trusted-host=b use-deprecated=b
20
+ use-feature=b].freeze,
21
+ cache: %w[format=b].freeze,
22
+ completion: %w[b|bash f|fish p|powershell z|zsh].freeze,
23
+ config: %w[global user site editor=p].freeze,
24
+ debug: %w[abi=b implementation=b platform=b python-version=b].freeze,
25
+ download: %w[d|dest=p].freeze,
26
+ freeze: %w[all exclude-editable l|local user exclude=b path=p r|requirement=p].freeze,
27
+ index: %w[json].freeze,
28
+ inspect: %w[local user path=p].freeze,
29
+ install: %w[break-system-packages compile dry-run force-reinstall I|ignore-installed no-compile
30
+ no-warn-conflicts no-warn-script-location U|upgrade user prefix=p report=p root=p
31
+ root-user-action=b t|target=p upgrade-strategy=b].freeze,
32
+ install_a: %w[ignore-requires-python no-index pre extra-index-url=q f|find-links=q i|index-url=q no-binary=q
33
+ only-binary=q].freeze,
34
+ install_b: %w[check-build-dependencies no-build-isolation no-clean no-deps prefer-binary require-hashes
35
+ use-pep517 c|constraint=p group=q progress-bar=b r|requirement=p src=p].freeze,
36
+ install_c: %w[C|config-settings=q e|editable=v].freeze,
37
+ hash: %w[a|algorithm].freeze,
38
+ list: %w[e|editable exclude-editable include-editable l|local no-index not-required o|outdated pre u|uptodate
39
+ user exclude=b extra-index-url=q format=b f|find-links=q i|index-url=q path=p].freeze,
40
+ lock: %w[o|output=p].freeze,
41
+ show: %w[f|files].freeze,
27
42
  uninstall: %w[break-system-packages y|yes r|requirement=p root-user-action=b].freeze,
28
- freeze: %w[all exclude-editable l|local user exclude=b path=p r|requirement=p].freeze
43
+ wheel: %w[no-verify w|wheel-dir=p].freeze
29
44
  }.freeze
30
45
  OPT_POETRY = {
31
46
  common: %w[ansi no-ansi no-cache n|no-interaction no-plugins q|quiet v|verbose P|project=p].freeze,
@@ -52,11 +67,18 @@ module Squared
52
67
  client-cert=p c|comment=q config-file=p i|identity=b p|password=q r|repository=b repository-url=q
53
68
  sign-with=b u|username=q].freeze
54
69
  }.freeze
55
- private_constant :DEP_PYTHON, :DIR_PYTHON, :OPT_PYTHON, :OPT_PIP, :OPT_POETRY, :OPT_PDM, :OPT_HATCH, :OPT_TWINE
70
+ PASS_PYTHON = {
71
+ pip: {
72
+ debug: %w[platform].freeze,
73
+ install: %w[C config-settings c constraint extra-index-url no-binary only-binary platform
74
+ r requirement].freeze,
75
+ list: %w[exclude extra-index-url].freeze
76
+ }.freeze
77
+ }.freeze
78
+ private_constant :DEP_PYTHON, :DIR_PYTHON, :OPT_PYTHON, :OPT_PIP, :OPT_POETRY, :OPT_PDM, :OPT_HATCH, :OPT_TWINE,
79
+ :PASS_PYTHON
56
80
 
57
81
  class << self
58
- def populate(*); end
59
-
60
82
  def tasks
61
83
  [:outdated].freeze
62
84
  end
@@ -94,10 +116,10 @@ module Squared
94
116
 
95
117
  subtasks({
96
118
  'venv' => %i[exec create remove show].freeze,
97
- 'pip' => %i[upgrade uninstall freeze].freeze,
119
+ 'pip' => %i[upgrade uninstall wheel reinstall freeze].freeze,
98
120
  'install' => %i[user force upgrade target editable].freeze,
99
121
  'outdated' => %i[major minor patch].freeze,
100
- 'build' => %i[python poetry pdm hatch].freeze,
122
+ 'build' => %i[poetry pdm hatch python].freeze,
101
123
  'publish' => %i[poetry pdm hatch twine].freeze,
102
124
  'run' => nil,
103
125
  'exec' => nil
@@ -132,7 +154,7 @@ module Squared
132
154
  format_desc action, nil, "script+|#{indexchar}index+|#,pattern*"
133
155
  task action, [:command] do |_, args|
134
156
  found = 0
135
- %w[tool.poetry.scripts tool.pdm.scripts project.scripts].each_with_index do |table, index|
157
+ %w[tool.poetry.scripts tool.pdm.scripts project.scripts].each_with_index do |table, i|
136
158
  next if (list = read_pyproject(table)).empty?
137
159
 
138
160
  if args.command == '#'
@@ -141,8 +163,8 @@ module Squared
141
163
  else
142
164
  args.to_a.each do |val|
143
165
  if (n, = indexitem(val))
144
- if (script, = list[n - 1])
145
- case index
166
+ if (script, = list[n.pred])
167
+ case i
146
168
  when 0
147
169
  script = session_output 'poetry', 'run', script
148
170
  when 1
@@ -159,7 +181,7 @@ module Squared
159
181
  log.warn "run script #{n} of #{list.size} (out of range)"
160
182
  end
161
183
  else
162
- case index
184
+ case i
163
185
  when 0
164
186
  found |= 1
165
187
  run(session_output('poetry', 'run', val), from: :run)
@@ -183,24 +205,21 @@ module Squared
183
205
  end
184
206
  end
185
207
  when 'exec'
186
- format_desc action, nil, 'command|:,args*'
208
+ format_desc action, nil, ':|command,args*'
187
209
  task action do |_, args|
188
- i = (args = args.to_a).delete(':')
189
- cmd = if i && !workspace.windows?
190
- readline('Enter script', force: true, multiline: ['##', ';'])
210
+ args = args.to_a
211
+ cmd = if (i = args.delete(':')) && !workspace.windows?
212
+ readline('Enter script', force: true, multiline: %w[## ;])
191
213
  elsif i || args.empty?
192
214
  readline('Enter command', force: true)
193
215
  else
194
- if (val = command_args(args, min: 1, prefix: 'python'))
195
- args << val
196
- end
197
- args.join(' ')
216
+ (args << command_args(args, min: 1, prefix: 'python')).compact.join(' ')
198
217
  end
199
218
  shell(cmd, name: :exec, chdir: path)
200
219
  end
201
220
  end
202
221
  else
203
- namespace action do
222
+ namespace action do |ns|
204
223
  flags.each do |flag|
205
224
  case action
206
225
  when 'venv'
@@ -215,11 +234,11 @@ module Squared
215
234
  when :remove
216
235
  next unless projectpath?(venv)
217
236
 
218
- format_desc action, flag, 'c|create?,d|depend?'
237
+ format_desc action, flag, 'c/reate?,d/epend?'
219
238
  task flag do |_, args|
220
239
  rm_rf(venv, verbose: true)
221
- venv_init if has_value?(%w[c create], args.to_a)
222
- depend if has_value?(%w[d depend], args.to_a)
240
+ venv_init if has_value?(args = args.to_a, 'c', 'create')
241
+ depend if has_value?(args, 'd', 'depend')
223
242
  end
224
243
  when :exec
225
244
  format_desc action, flag, 'command,args*'
@@ -227,7 +246,7 @@ module Squared
227
246
  args = args.to_a
228
247
  if args.empty?
229
248
  args = readline('Enter command', force: true).split(' ', 2)
230
- elsif args.size == 1 && !option('interactive', prefix: 'venv', equals: '0')
249
+ elsif args.size == 1 && !option('interactive', equals: '0', prefix: ref)
231
250
  args << readline('Enter arguments', force: false)
232
251
  end
233
252
  venv_init
@@ -250,21 +269,35 @@ module Squared
250
269
  when :freeze
251
270
  format_desc action, flag, "file?=#{DEP_PYTHON[4]},opts*"
252
271
  task flag do |_, args|
253
- if (file = pip(flag, args.to_a)) && verbose
272
+ if (file = pip(flag, opts: args.to_a, banner: true)) && verbose
254
273
  puts File.read(file)
255
274
  end
256
275
  end
257
276
  when :uninstall
258
277
  format_desc action, flag, 'package+,opts*'
259
278
  task flag do |_, args|
260
- pip flag, args.to_a
279
+ pip(flag, opts: args.to_a, banner: true)
280
+ end
281
+ when :wheel
282
+ next unless pyprojectfile || setuptools?
283
+
284
+ format_desc action, flag, 'opts*,args*'
285
+ task flag do |_, args|
286
+ pip(flag, opts: args.to_a, banner: true)
287
+ end
288
+ when :reinstall
289
+ if venv && projectpath?(venv)
290
+ format_desc action, flag
291
+ task flag do
292
+ ns['venv:remove'].invoke('depend')
293
+ end
261
294
  end
262
295
  end
263
296
  when 'install'
264
297
  format_desc(action, flag, 'opts*', before: case flag
265
298
  when :target then 'dir'
266
- when :editable then 'path/url?'
267
- when :upgrade then 'strategy?,package+'
299
+ when :editable then 'path/url?,opts*'
300
+ when :upgrade then 'strategy?,opts*,package+'
268
301
  end)
269
302
  case flag
270
303
  when :editable
@@ -273,17 +306,18 @@ module Squared
273
306
  end
274
307
  when :upgrade
275
308
  task flag, [:strategy] do |_, args|
276
- args = case (strategy = args.strategy)
277
- when 'eager', 'only-if-needed'
278
- args.extras
279
- when 'needed'
280
- strategy = 'only-if-needed'
281
- args.extras
282
- else
283
- strategy = nil
284
- args.to_a
285
- end
286
- install(flag, args, strategy: strategy)
309
+ install flag, (case args.strategy
310
+ when 'eager'
311
+ 'eager'
312
+ when /(?:\Aonly-if|needed\z)/
313
+ 'only-if-needed'
314
+ end.yield_self do |val|
315
+ if val
316
+ args.extras << "upgrade-strategy=#{val}"
317
+ else
318
+ args.to_a
319
+ end
320
+ end)
287
321
  end
288
322
  when :target
289
323
  task flag, [:dir] do |_, args|
@@ -296,30 +330,26 @@ module Squared
296
330
  end
297
331
  end
298
332
  when 'outdated'
299
- format_desc action, flag, 'eager?,user?'
333
+ format_desc action, flag, 'eager?,user?,s/elect'
300
334
  task flag do |_, args|
301
335
  outdated flag, args.to_a
302
336
  end
303
337
  when 'build'
304
- case flag
305
- when :poetry
306
- next unless build_backend == 'poetry.core.masonry.api'
307
- when :pdm
308
- next unless build_backend == 'pdm.backend'
309
- when :hatch
310
- next unless build_backend == 'hatchling.build'
311
- end
338
+ next if (be = backend?(flag)) == false
339
+
312
340
  format_desc(action, flag, 'opts*', after: case flag
313
- when :python then 'srcdir?'
314
341
  when :poetry then 'output?'
315
342
  when :pdm then 'dest?'
316
343
  when :hatch then 'location?'
344
+ else 'srcdir?'
317
345
  end)
318
346
  task flag do |_, args|
319
347
  build! flag, args.to_a
320
348
  end
321
- break unless flag == :python
349
+ break if be
322
350
  when 'publish'
351
+ next if (be = backend?(flag)) == false
352
+
323
353
  format_desc(action, flag, 'test?,opts*', after: case flag
324
354
  when :hatch then 'artifacts?'
325
355
  when :twine then 'dist?'
@@ -333,6 +363,7 @@ module Squared
333
363
  false
334
364
  end)
335
365
  end
366
+ break if be
336
367
  end
337
368
  end
338
369
  end
@@ -361,11 +392,12 @@ module Squared
361
392
  when :force
362
393
  cmd << '--force-reinstall'
363
394
  end
364
- append_pip(flag, opts, from: :install)
395
+ op = append_pip(flag, opts, from: :install)
396
+ op.clear
365
397
  else
366
398
  append_global
367
399
  end
368
- cmd << "-r #{DEP_PYTHON[4]}" if basepath(DEP_PYTHON[4]).exist? && !session_arg?('r', 'requirement')
400
+ cmd << "-r #{DEP_PYTHON[4]}" if exist?(DEP_PYTHON[4]) && !session_arg?('r', 'requirement')
369
401
  append_editable
370
402
  end
371
403
  run(from: :depend, sync: sync)
@@ -374,18 +406,24 @@ module Squared
374
406
 
375
407
  def outdated(flag = nil, opts = [], sync: invoked_sync?('outdated', flag))
376
408
  cmd = pip_session 'list --outdated'
409
+ cmd << '--not-required' unless flag || option('not-required', equals: '0')
410
+ cmd << '--local' if option('l', 'local')
377
411
  append_global
378
412
  cmd = session_done cmd
379
413
  log.info cmd
380
414
  on :first, :outdated
381
415
  banner = format_banner cmd
382
- print_item banner if sync
383
- start = 0
384
- found = 0
385
- major = []
386
- minor = []
387
- patch = []
416
+ items = if sync
417
+ print_item banner
418
+ [] if flag && has_value?(opts, 's', 'select') && !stdin?
419
+ end
388
420
  pwd_set(from: :outdated) do
421
+ tc = theme[:current]
422
+ start = 0
423
+ found = 0
424
+ major = []
425
+ minor = []
426
+ patch = []
389
427
  buffer = []
390
428
  out = ->(val) { sync ? puts(val) : buffer << val }
391
429
  if workspace.windows?
@@ -419,26 +457,27 @@ module Squared
419
457
  styles = color(:yellow)
420
458
  else
421
459
  styles = color(:green)
422
- line = sub_style(line, pat: /^(\S+)(.+)$/, styles: if type == 2
423
- styles << :bold
424
- theme[:major]
425
- else
426
- theme[:active]
427
- end)
428
- end
429
- if theme[:current]
430
- line = sub_style(line, pat: /^(.+)(#{Regexp.escape(current)})(.+)$/, styles: theme[:current],
431
- index: 2)
460
+ line = sub_style(line, styles: if type == 2
461
+ styles += [:bold]
462
+ theme[:major]
463
+ else
464
+ theme[:active]
465
+ end, pat: /^(\S+)(.+)$/)
432
466
  end
433
- line = sub_style(line, pat: /^(.+)(#{Regexp.escape(latest)})(.+)$/, styles: styles, index: 2)
467
+ line = sub_style(line, **opt_style(tc, /^(.+)(#{Regexp.escape(current)})(.+)$/, 2)) if tc
468
+ line = sub_style(line, **opt_style(styles, /^(.+)(#{Regexp.escape(latest)})(.+)$/, 2))
434
469
  found += 1
435
470
  end
436
- out.call("#{start.to_s.rjust(2)}. #{line}")
471
+ if items
472
+ items << [line, name]
473
+ else
474
+ out.call("#{start.to_s.rjust(2)}. #{line}")
475
+ end
437
476
  start += 1
438
477
  elsif line.start_with?('Package')
439
478
  unless stdin?
440
- sub = { pat: /^(.*)(?<!\dm)(Package|Latest)(.+)$/, styles: theme[:header], index: 2 }
441
- out.call(print_footer(" # #{line.chomp}", reverse: true, sub: [sub, sub]))
479
+ sub = [opt_style(theme[:header], /^(.*)(?<!\dm)(Package|Latest)(.+)$/, 2)] * 2
480
+ out.call(print_footer(" # #{line.chomp}", reverse: true, sub: sub))
442
481
  end
443
482
  start += 1
444
483
  end
@@ -448,17 +487,23 @@ module Squared
448
487
  puts buffer
449
488
  end
450
489
  if found > 0
451
- print_status(major.size, minor.size, patch.size, from: :outdated)
452
- pkg = case flag
453
- when :major
454
- major + minor + patch
455
- when :minor
456
- minor + patch
457
- when :patch
458
- patch
459
- end
460
- unless !pkg || pkg.empty?
461
- install(:upgrade, pkg, strategy: opts.include?('eager') ? 'eager' : nil, user: opts.include?('user'))
490
+ if items
491
+ choice('Select a package', items.map(&:first),
492
+ multiple: true, force: false, index: true, border: true).map! { |n| items[n.pred].last }
493
+ else
494
+ case flag
495
+ when :major
496
+ major + minor + patch
497
+ when :minor
498
+ minor + patch
499
+ else
500
+ patch
501
+ end
502
+ end.tap do |packages|
503
+ unless packages.empty?
504
+ install(:upgrade, opts.grep(/^(?:eager|user)$/), packages: packages, banner: false)
505
+ end
506
+ print_status(major.size, minor.size, patch.size, from: :outdated)
462
507
  end
463
508
  elsif start == 0
464
509
  puts 'No updates were found'
@@ -467,43 +512,41 @@ module Squared
467
512
  on :last, :outdated
468
513
  end
469
514
 
470
- def install(flag, opts = [], strategy: nil, user: nil)
471
- cmd = pip_session 'install'
472
- out = append_pip(flag, opts, from: :install)
515
+ def install(flag, opts = [], packages: [], banner: true)
516
+ op = append_pip(flag, opts, target: pip_session('install'), from: :install)
473
517
  case flag
474
518
  when :editable
475
- cmd << quote_option('e', out.pop || editable || '.')
476
- option_clear out
519
+ op << quote_option('e', op.pop || editable || '.')
520
+ op.clear
477
521
  when :upgrade
478
- raise_error('no packages listed', hint: flag) if out.empty?
479
- cmd << '--upgrade'
480
- cmd << '--user' if user
481
- cmd << basic_option('upgrade-strategy', strategy) if strategy
482
- append_value out
483
- python_session('-m pip', *cmd.to_a.drop(1)) if workspace.windows?
522
+ op.concat(packages)
523
+ raise_error 'no packages listed', hint: flag if op.empty?
524
+ op << '--upgrade'
525
+ op.append
526
+ python_session('-m pip', *op.to_a.drop(1)) if workspace.windows?
484
527
  end
485
- run(from: :install)
528
+ run(banner: banner, from: :install)
486
529
  end
487
530
 
488
531
  def build!(flag, opts = [])
489
- case flag
490
- when :python
491
- cmd, opts = python_session('-m build', opts: opts)
492
- list = OPT_PYTHON[:build]
493
- when :poetry
494
- cmd = poetry_session 'build'
495
- list = OPT_POETRY[:build] + OPT_POETRY[:common]
496
- when :pdm
497
- cmd, opts = pdm_session('build', opts: opts)
498
- list = OPT_PDM[:build]
499
- when :hatch
500
- cmd, opts = hatch_session('build', opts: opts)
501
- list = OPT_HATCH[:build]
502
- end
532
+ list = case flag
533
+ when :poetry
534
+ cmd = poetry_session 'build'
535
+ OPT_POETRY[:build] + OPT_POETRY[:common]
536
+ when :pdm
537
+ cmd, opts = pdm_session('build', opts: opts)
538
+ OPT_PDM[:build]
539
+ when :hatch
540
+ cmd, opts = hatch_session('build', opts: opts)
541
+ OPT_HATCH[:build]
542
+ else
543
+ cmd, opts = python_session('-m build', opts: opts)
544
+ OPT_PYTHON[:build]
545
+ end
503
546
  srcdir = nil
504
547
  op = OptionPartition.new(opts, list, cmd, project: self, single: singleopt(flag))
505
548
  op.each do |opt|
506
- if !srcdir && basepath(opt.chomp('*')).exist? && projectpath?(opt.chomp('*'))
549
+ if !srcdir && exist?(opt.chomp('*')) && projectpath?(opt.chomp('*'))
507
550
  srcdir = opt
508
551
  else
509
552
  op.found << opt
@@ -513,7 +556,7 @@ module Squared
513
556
  case flag
514
557
  when :poetry, :pdm
515
558
  if srcdir
516
- args = flag == :pdm ? ['d', 'dest'] : ['o', 'output']
559
+ args = flag == :pdm ? %w[d dest] : %w[o output]
517
560
  if op.arg?(*args)
518
561
  op.push(srcdir)
519
562
  else
@@ -535,24 +578,29 @@ module Squared
535
578
  end
536
579
 
537
580
  def publish(flag, opts = [], test: false)
538
- case flag
539
- when :poetry
540
- poetry_session 'publish'
541
- list = OPT_POETRY[:publish] + OPT_POETRY[:common]
542
- when :pdm
543
- opts = pdm_session('publish', opts: opts).last
544
- list = OPT_PDM[:publish]
545
- when :hatch
546
- opts = hatch_session('publish', opts: opts).last
547
- list = OPT_HATCH[:publish]
548
- when :twine
549
- session 'twine', 'upload'
550
- list = OPT_TWINE[:publish]
551
- end
581
+ list = case flag
582
+ when :poetry
583
+ poetry_session 'publish'
584
+ OPT_POETRY[:publish] + OPT_POETRY[:common]
585
+ when :pdm
586
+ opts = pdm_session('publish', opts: opts).last
587
+ OPT_PDM[:publish]
588
+ when :hatch
589
+ opts = hatch_session('publish', opts: opts).last
590
+ OPT_HATCH[:publish]
591
+ else
592
+ session 'twine', 'upload'
593
+ OPT_TWINE[:publish]
594
+ end
552
595
  op = OptionPartition.new(opts, list, @session, project: self, single: singleopt(flag))
553
596
  dist = lambda do
554
- basepath('dist').tap do |dir|
555
- raise_error('no source files found', hint: dir) unless dir.directory? && !dir.empty?
597
+ dir = basepath 'dist'
598
+ return dir if dir.directory? && !dir.empty?
599
+
600
+ if dir.exist?
601
+ raise_error 'no source to publish', hint: dir
602
+ else
603
+ raise_error Errno::ENOENT, dir, hint: 'publish'
556
604
  end
557
605
  end
558
606
  if test
@@ -563,35 +611,93 @@ module Squared
563
611
  end
564
612
  end
565
613
  case flag
566
- when :hatch, :twine
614
+ when :poetry, :pdm
615
+ dist.call unless op.arg?(*(flag == :poetry ? ['dist-dir'] : %w[d dest]))
616
+ op.clear(pass: false)
617
+ else
567
618
  if op.empty?
568
- op.push("#{dist.call}/*")
619
+ op << "#{dist.call}/*"
569
620
  else
570
- op.map! { |val| basepath(val) }
621
+ op.add_path
571
622
  end
572
- op.append
573
- else
574
- dist.call unless op.arg?(*(flag == :poetry ? ['dist-dir'] : ['d', 'dest']))
575
- op.clear(pass: false)
576
623
  end
577
- run(from: :"#{flag}:publish", interactive: "Publish #{sub_style(project, styles: theme[:active])}")
624
+ run(from: :"#{flag}:publish", interactive: ['Publish', 'N', project])
578
625
  end
579
626
 
580
- def pip(flag, opts = [])
581
- cmd = pip_session flag
582
- out = append_pip(nil, opts, from: flag)
627
+ def pip(flag, *args, sync: true, banner: verbose?, with: nil, pass: nil, **kwargs)
628
+ flag = flag.to_sym
629
+ pass = PASS_PYTHON[:pip].fetch(pip_install?(flag) ? :install : flag, []) + %w[v verbose] if pass.nil?
630
+ opts = session_opts(with, args: args, kwargs: kwargs, pass: pass)
583
631
  case flag
584
- when :uninstall
585
- raise_error('no packages listed', hint: flag) if out.empty?
586
- cmd.merge(out)
632
+ when :freeze, :inspect, :list, :check, :completion, :debug
633
+ opts.concat(args)
634
+ end
635
+ op = append_pip(flag, opts, target: pip_session(flag), from: flag)
636
+ case flag
637
+ when :install, :uninstall, :show, :index
638
+ op.concat(args)
639
+ if op.empty?
640
+ case flag
641
+ when :install, :uninstall
642
+ op << '.' if installable? && !op.arg?('r', 'requirement')
643
+ else
644
+ raise_error 'no packages listed', hint: flag
645
+ end
646
+ elsif flag == :install
647
+ op.append_any
648
+ elsif flag == :index
649
+ op.adjoin('versions', with: 'index')
650
+ .add_first
651
+ .clear
652
+ else
653
+ op.append
654
+ end
587
655
  when :freeze
588
656
  venv_init
589
- ret = basepath(out.shift || DEP_PYTHON[4])
590
- cmd << '>' << shell_quote(ret)
591
- option_clear out
657
+ op << '>'
658
+ op.add_quote(ret = basepath(op.detect { |val| op.exist?(val) } || DEP_PYTHON[4]))
659
+ .clear
660
+ when :cache
661
+ op.concat(args)
662
+ raise_error 'no subcommand', hint: flag if op.empty?
663
+ op << (action = op.shift)
664
+ case action
665
+ when 'dir', 'info', 'purge'
666
+ nil
667
+ when 'list', 'remove'
668
+ op.add_first(quote: true)
669
+ else
670
+ raise_error ArgumentError, "unrecognized args: #{action}", hint: flag
671
+ end
672
+ op.clear
673
+ when :config
674
+ op.concat(args)
675
+ raise_error 'no subcommand', hint: flag if op.empty?
676
+ op << (action = op.shift)
677
+ case action
678
+ when 'list', 'edit', 'debug'
679
+ nil
680
+ when 'get', 'unset', 'set'
681
+ op.add_first
682
+ op.add_first(quote: true, expect: true) if action == 'set'
683
+ else
684
+ raise_error ArgumentError, "unrecognized args: #{action}", hint: flag
685
+ end
686
+ op.clear
687
+ when :hash
688
+ op.append(projectmap(op.concat(args), parent: true))
689
+ when :wheel, :lock, :download
690
+ op.concat(args)
691
+ if !op.empty?
692
+ op.append
693
+ elsif installable? && !op.arg?('r', 'requirement')
694
+ op << '.'
695
+ end
696
+ else
697
+ op.clear
592
698
  end
593
- run(from: :"pip:#{flag}")
594
- ret
699
+ print_run(op, banner, **kwargs)
700
+ run(sync: sync, banner: banner, from: :"pip:#{flag}").yield_self { |val| ret || val }
595
701
  end
596
702
 
597
703
  def variable_set(key, *val, **, &blk)
@@ -613,7 +719,7 @@ module Squared
613
719
  when :editable
614
720
  editable_set val.first
615
721
  when :venv
616
- instance_variable_set(:"@#{key}", val.empty? ? nil : basepath(*val))
722
+ instance_variable_set(:"@#{key}", (basepath(*val) unless val.empty?))
617
723
  else
618
724
  super
619
725
  end
@@ -637,14 +743,13 @@ module Squared
637
743
  return session('python', *preopts(quiet: false), *cmd, path: venv.nil?) unless opts
638
744
 
639
745
  op = OptionPartition.new(opts, OPT_PYTHON[:common], project: self, single: /\A(?:v+|OO)\z/)
640
- ret = session('python', *op.to_a, *cmd, path: venv.nil?)
641
- [ret, op.extras]
746
+ [session('python', *op.to_a, *cmd, path: venv.nil?), op.extras]
642
747
  end
643
748
 
644
749
  def poetry_session(*cmd)
645
- ret = session('poetry', *cmd, *preopts)
646
- option('project', ignore: false) { |val| ret << quote_option('project', basepath(val)) }
647
- ret
750
+ session('poetry', *cmd, *preopts).tap do |ret|
751
+ option('project', ignore: false) { |val| ret << quote_option('project', basepath(val)) }
752
+ end
648
753
  end
649
754
 
650
755
  def pdm_session(*cmd, opts: nil)
@@ -659,18 +764,28 @@ module Squared
659
764
  return session(name, *preopts, *cmd, path: venv.nil?) unless opts
660
765
 
661
766
  op = OptionPartition.new(opts, common, project: self, single: singleopt)
662
- ret = session(name, *op.to_a, *cmd, path: venv.nil?)
663
- [ret, op.extras]
767
+ [session(name, *op.to_a, *cmd, path: venv.nil?), op.extras]
664
768
  end
665
769
 
666
770
  def append_pip(flag, opts, target: @session, from: nil)
667
- unless from && !opts.empty?
668
- append_global(target: target)
669
- return []
771
+ list = OPT_PIP.fetch(from, []) + OPT_PIP[:common]
772
+ if pip_install?(flag)
773
+ list.concat(OPT_PIP[:install_a])
774
+ list.concat(OPT_PIP[:install_b]) unless flag == :index
775
+ case flag
776
+ when :install
777
+ list.concat(OPT_PIP[:install_c] + OPT_PIP[:debug])
778
+ when :lock, :wheel
779
+ list.concat(OPT_PIP[:install_c])
780
+ when :download, :index
781
+ list.concat(OPT_PIP[:debug])
782
+ end
783
+ opts << 'no-build-isolation' if option('build-isolation', equals: '0')
670
784
  end
671
- op = OptionPartition.new(opts, OPT_PIP[from] + OPT_PIP[:common], target, project: self, single: singleopt)
785
+ op = OptionPartition.new(opts, list, target, project: self, single: singleopt)
672
786
  append_global(target: target)
673
- if from == :install
787
+ case flag
788
+ when :install, :lock, :wheel
674
789
  edit = nil
675
790
  op.each do |opt|
676
791
  if opt =~ op.values
@@ -685,7 +800,6 @@ module Squared
685
800
  op.found << opt
686
801
  end
687
802
  end
688
- op << '--no-build-isolation' if option('build-isolation', equals: '0')
689
803
  op.swap
690
804
  if edit
691
805
  edit = basepath(edit) unless %r{\A[a-z]+(?:\+[a-z]+)?://}i.match?(edit)
@@ -695,16 +809,8 @@ module Squared
695
809
  op << quote_option('e', edit)
696
810
  end
697
811
  end
698
- case flag
699
- when :editable, :upgrade
700
- op.extras
701
- else
702
- op.clear
703
- []
704
- end
705
- else
706
- op.extras
707
812
  end
813
+ op
708
814
  end
709
815
 
710
816
  def append_editable(target: @session)
@@ -725,16 +831,18 @@ module Squared
725
831
  end
726
832
 
727
833
  def append_global(target: @session)
728
- option('cache-dir', target: target) do |val|
729
- target << case val
730
- when '0', 'false'
731
- '--no-cache-dir'
732
- else
733
- quote_option('cache-dir', basepath(val))
734
- end
735
- end
736
- option('proxy', target: target) { |val| target << shell_option('proxy', val) }
737
- option('python', target: target) { |val| target << quote_option('python', basepath(val)) }
834
+ target.merge([
835
+ option('cache-dir', target: target) do |val|
836
+ case val
837
+ when '0', 'false'
838
+ '--no-cache-dir'
839
+ else
840
+ quote_option 'cache-dir', basepath(val)
841
+ end
842
+ end,
843
+ option('proxy', target: target) { |val| shell_option('proxy', val) },
844
+ option('python', target: target) { |val| quote_option('python', basepath(val)) }
845
+ ])
738
846
  append_nocolor(target: target)
739
847
  end
740
848
 
@@ -783,13 +891,13 @@ module Squared
783
891
  end
784
892
  end
785
893
  else
786
- val = case (val = data[2])
894
+ val = case (ch = data[2])
787
895
  when 'true'
788
896
  true
789
897
  when 'false'
790
898
  false
791
899
  else
792
- val.include?('.') ? val.to_f : val.to_i
900
+ ch.include?('.') ? ch.to_f : ch.to_i
793
901
  end
794
902
  end
795
903
  ret << [data[1], val]
@@ -806,9 +914,9 @@ module Squared
806
914
  end
807
915
 
808
916
  def pyprojectfile
809
- return unless (ret = basepath(DEP_PYTHON[2])).exist?
917
+ return @pyprojectfile || nil unless @pyprojectfile.nil?
810
918
 
811
- ret
919
+ @pyprojectfile = (file = basepath(DEP_PYTHON[2])).exist? ? file : false
812
920
  end
813
921
 
814
922
  def singleopt(flag = nil)
@@ -823,14 +931,14 @@ module Squared
823
931
  end
824
932
 
825
933
  def preopts(quiet: true)
826
- ret = []
827
- case verbose
828
- when FalseClass
829
- ret << '--quiet' if quiet
830
- when Numeric
831
- ret << "-#{'v' * verbose}" if verbose > 0
934
+ [].tap do |ret|
935
+ case verbose
936
+ when FalseClass
937
+ ret << '--quiet' if quiet
938
+ when Numeric
939
+ ret << "-#{'v' * verbose}" if verbose > 0
940
+ end
832
941
  end
833
- ret
834
942
  end
835
943
 
836
944
  def variables
@@ -900,6 +1008,27 @@ module Squared
900
1008
  puts(dir.directory? ? "Success: #{dir}" : 'Failed') if banner && !status
901
1009
  end
902
1010
 
1011
+ def pip_install?(flag)
1012
+ %i[install download index lock wheel].include?(flag)
1013
+ end
1014
+
1015
+ def backend?(flag)
1016
+ case flag
1017
+ when :poetry
1018
+ build_backend == 'poetry.core.masonry.api'
1019
+ when :pdm
1020
+ build_backend == 'pdm.backend'
1021
+ when :hatch
1022
+ build_backend == 'hatchling.build'
1023
+ when :setuptools
1024
+ build_backend == 'setuptools.build_meta'
1025
+ end
1026
+ end
1027
+
1028
+ def installable?
1029
+ setuptools? || !pyprojectfile.nil?
1030
+ end
1031
+
903
1032
  def setuptools?
904
1033
  dependtype == 2 || dependtype == 4
905
1034
  end