squared 0.5.25 → 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.
@@ -8,37 +8,52 @@ module Squared
8
8
  DIR_PYTHON = (DEP_PYTHON + %w[README.rst]).freeze
9
9
  OPT_PYTHON = {
10
10
  common: %w[b B d E h i I O P q s S u v x c=q m=b W=b X=q check-hash-based-pycs=b].freeze,
11
- build: %w[C=bm n|no-isolation s|sdist x|skip-dependency-check v|verbose w|wheel config-setting=q installer=b
11
+ build: %w[n|no-isolation s|sdist x|skip-dependency-check v|verbose w|wheel C|config-setting=q installer=b
12
12
  o|outdir=p].freeze,
13
13
  venv: %w[clear copies symlinks system-site-packages upgrade upgrade-deps without-scm-ignore-files without-pip
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,
32
47
  build: %w[clean config-settings=qq f|format=b o|output=p].freeze,
33
- publish: %w[build dry-run skip-existing cert=p client-cert=p dist-dir=p p|password=q r|repository=q
34
- u|username=qq].freeze
48
+ publish: %w[build dry-run skip-existing cert=p client-cert=p dist-dir=p p|password=b r|repository=q
49
+ u|username=b].freeze
35
50
  }.freeze
36
51
  OPT_PDM = {
37
52
  common: %w[I|ignore-python no-cache n|non-interactive].freeze,
38
53
  build: %w[C=bm no-clean no-isolation no-sdist no-wheel quiet verbose config-setting=q d|dest=p p|project=p
39
54
  k|skip=b].freeze,
40
55
  publish: %w[no-build no-very-ssl quiet S|sign skip-existing verbose ca-certs=p c|comment=q d|dest=p
41
- i|identity=b P|password=q p|project=p r|repository=q k|skip=b u|username=qq].freeze
56
+ i|identity=b P|password=q p|project=p r|repository=q k|skip=b u|username=b].freeze
42
57
  }.freeze
43
58
  OPT_HATCH = {
44
59
  common: %w[color interactive no-color no-interactive cache-dir=p config=p data-dir=p e|env=b p|project=b
@@ -50,13 +65,20 @@ module Squared
50
65
  OPT_TWINE = {
51
66
  publish: %w[attestations disable-progress-bar non-interactive s|sign skip-existing verbose cert=p
52
67
  client-cert=p c|comment=q config-file=p i|identity=b p|password=q r|repository=b repository-url=q
53
- sign-with=b u|username=qq].freeze
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
@@ -78,7 +100,7 @@ module Squared
78
100
 
79
101
  attr_reader :venv, :editable
80
102
 
81
- def initialize(*, editable: '.', asdf: 'python', **kwargs)
103
+ def initialize(*, editable: '.', verbose: nil, asdf: 'python', **kwargs)
82
104
  super
83
105
  if @pass.include?(Python.ref)
84
106
  initialize_ref Python.ref
@@ -87,17 +109,17 @@ module Squared
87
109
  initialize_build(Python.ref, **kwargs)
88
110
  initialize_env(**kwargs)
89
111
  end
90
- dependfile_set(DEP_PYTHON, default: 2)
112
+ dependfile_set DEP_PYTHON
91
113
  editable_set editable
92
114
  venv_set kwargs[:venv]
93
115
  end
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)
@@ -176,31 +198,28 @@ module Squared
176
198
  end
177
199
  break
178
200
  end
179
- next if found.anybits?(1)
180
-
181
- puts log_message(found == 0 ? Logger::INFO : Logger::WARN,
182
- "no scripts #{found == 0 ? 'found' : 'executed'}",
183
- subject: name, hint: pyprojectfile)
201
+ unless found.anybits?(1)
202
+ puts log_message(found == 0 ? Logger::INFO : Logger.WARN,
203
+ "no scripts #{found == 0 ? 'found' : 'executed'}",
204
+ subject: name, hint: pyprojectfile)
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
- run(cmd, send: :exec, banner: false)
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,8 +246,8 @@ 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')
231
- args << readline('Enter arguments', force: false) unless args.first.include?(' ')
249
+ elsif args.size == 1 && !option('interactive', equals: '0', prefix: ref)
250
+ args << readline('Enter arguments', force: false)
232
251
  end
233
252
  venv_init
234
253
  run args.join(' ')
@@ -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
341
  when :poetry then 'output?'
314
342
  when :pdm then 'dest?'
315
343
  when :hatch then 'location?'
316
- else 'outdir?'
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
@@ -352,7 +383,6 @@ module Squared
352
383
  cmd << '--no-root' if option('no-root')
353
384
  else
354
385
  cmd = pip_session 'install'
355
- cmd << '--upgrade-strategy=eager' if env('PYTHON_UPDATE')
356
386
  if flag
357
387
  case flag
358
388
  when :user
@@ -362,11 +392,12 @@ module Squared
362
392
  when :force
363
393
  cmd << '--force-reinstall'
364
394
  end
365
- append_pip(flag, opts, from: :install)
395
+ op = append_pip(flag, opts, from: :install)
396
+ op.clear
366
397
  else
367
398
  append_global
368
399
  end
369
- 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')
370
401
  append_editable
371
402
  end
372
403
  run(from: :depend, sync: sync)
@@ -375,18 +406,24 @@ module Squared
375
406
 
376
407
  def outdated(flag = nil, opts = [], sync: invoked_sync?('outdated', flag))
377
408
  cmd = pip_session 'list --outdated'
409
+ cmd << '--not-required' unless flag || option('not-required', equals: '0')
410
+ cmd << '--local' if option('l', 'local')
378
411
  append_global
379
412
  cmd = session_done cmd
380
413
  log.info cmd
381
414
  on :first, :outdated
382
415
  banner = format_banner cmd
383
- print_item banner if sync
384
- start = 0
385
- found = 0
386
- major = []
387
- minor = []
388
- patch = []
416
+ items = if sync
417
+ print_item banner
418
+ [] if flag && has_value?(opts, 's', 'select') && !stdin?
419
+ end
389
420
  pwd_set(from: :outdated) do
421
+ tc = theme[:current]
422
+ start = 0
423
+ found = 0
424
+ major = []
425
+ minor = []
426
+ patch = []
390
427
  buffer = []
391
428
  out = ->(val) { sync ? puts(val) : buffer << val }
392
429
  if workspace.windows?
@@ -420,26 +457,27 @@ module Squared
420
457
  styles = color(:yellow)
421
458
  else
422
459
  styles = color(:green)
423
- line = sub_style(line, pat: /^(\S+)(.+)$/, styles: if type == 2
424
- styles << :bold
425
- theme[:major]
426
- else
427
- theme[:active]
428
- end)
429
- end
430
- if theme[:current]
431
- line = sub_style(line, pat: /^(.+)(#{Regexp.escape(current)})(.+)$/, styles: theme[:current],
432
- 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+)(.+)$/)
433
466
  end
434
- 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))
435
469
  found += 1
436
470
  end
437
- 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
438
476
  start += 1
439
477
  elsif line.start_with?('Package')
440
478
  unless stdin?
441
- sub = { pat: /^(.*)(?<!\dm)(Package|Latest)(.+)$/, styles: theme[:header], index: 2 }
442
- 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))
443
481
  end
444
482
  start += 1
445
483
  end
@@ -449,17 +487,23 @@ module Squared
449
487
  puts buffer
450
488
  end
451
489
  if found > 0
452
- print_status(major.size, minor.size, patch.size, from: :outdated)
453
- pkg = case flag
454
- when :major
455
- major + minor + patch
456
- when :minor
457
- minor + patch
458
- when :patch
459
- patch
460
- end
461
- unless !pkg || pkg.empty?
462
- 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)
463
507
  end
464
508
  elsif start == 0
465
509
  puts 'No updates were found'
@@ -468,84 +512,95 @@ module Squared
468
512
  on :last, :outdated
469
513
  end
470
514
 
471
- def install(flag, opts = [], strategy: nil, user: nil)
472
- cmd = pip_session 'install'
473
- 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)
474
517
  case flag
475
518
  when :editable
476
- cmd << quote_option('e', out.pop || editable || '.')
477
- option_clear out
519
+ op << quote_option('e', op.pop || editable || '.')
520
+ op.clear
478
521
  when :upgrade
479
- raise_error('no packages listed', hint: flag) if out.empty?
480
- cmd << '--upgrade'
481
- cmd << '--user' if user
482
- cmd << basic_option('upgrade-strategy', strategy) if strategy
483
- append_value out
484
- 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?
485
527
  end
486
- run(from: :install)
528
+ run(banner: banner, from: :install)
487
529
  end
488
530
 
489
531
  def build!(flag, opts = [])
490
- case flag
491
- when :poetry
492
- cmd = poetry_session 'build'
493
- list = OPT_POETRY[:build] + OPT_POETRY[:common]
494
- when :pdm
495
- cmd, opts = pdm_session('build', opts: opts)
496
- list = OPT_PDM[:build]
497
- when :hatch
498
- cmd, opts = hatch_session('build', opts: opts)
499
- list = OPT_HATCH[:build]
500
- else
501
- cmd, opts = python_session('-m build', opts: opts)
502
- list = OPT_PYTHON[:build]
503
- 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
546
+ srcdir = nil
504
547
  op = OptionPartition.new(opts, list, cmd, project: self, single: singleopt(flag))
548
+ op.each do |opt|
549
+ if !srcdir && exist?(opt.chomp('*')) && projectpath?(opt.chomp('*'))
550
+ srcdir = opt
551
+ else
552
+ op.found << opt
553
+ end
554
+ end
555
+ op.swap
505
556
  case flag
506
- when :hatch
507
- if !ENV['HATCH_BUILD_LOCATION'] && (outdir ||= op.shift)
508
- op.add_path(outdir)
557
+ when :poetry, :pdm
558
+ if srcdir
559
+ args = flag == :pdm ? %w[d dest] : %w[o output]
560
+ if op.arg?(*args)
561
+ op.push(srcdir)
562
+ else
563
+ op << quote_option(args.last, basepath(srcdir))
564
+ end
565
+ srcdir = nil
509
566
  end
510
- else
511
- unless op.empty?
512
- args = case flag
513
- when :poetry
514
- %w[o output]
515
- when :pdm
516
- %w[d dest]
517
- else
518
- srcdir = true
519
- %w[o outdir]
520
- end
521
- op << quote_option(args.last, basepath(op.shift)) unless op.arg?(*args)
567
+ when :hatch
568
+ if ENV['HATCH_BUILD_LOCATION']
569
+ srcdir = nil
570
+ else
571
+ srcdir ||= path
522
572
  end
573
+ op << basic_option('p', project) unless ENV['HATCH_PROJECT'] || op.arg?('p', 'project')
523
574
  end
524
- op.exist?(add: true, first: true) if srcdir
575
+ op.add_path(srcdir) if srcdir
525
576
  op.clear
526
577
  run(from: :"#{flag}:build")
527
578
  end
528
- alias build_ build!
529
579
 
530
580
  def publish(flag, opts = [], test: false)
531
- case flag
532
- when :poetry
533
- poetry_session 'publish'
534
- list = OPT_POETRY[:publish] + OPT_POETRY[:common]
535
- when :pdm
536
- opts = pdm_session('publish', opts: opts).last
537
- list = OPT_PDM[:publish]
538
- when :hatch
539
- opts = hatch_session('publish', opts: opts).last
540
- list = OPT_HATCH[:publish]
541
- when :twine
542
- session 'twine', 'upload'
543
- list = OPT_TWINE[:publish]
544
- 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
545
595
  op = OptionPartition.new(opts, list, @session, project: self, single: singleopt(flag))
546
596
  dist = lambda do
547
- basepath('dist').tap do |dir|
548
- 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'
549
604
  end
550
605
  end
551
606
  if test
@@ -556,35 +611,93 @@ module Squared
556
611
  end
557
612
  end
558
613
  case flag
559
- 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
560
618
  if op.empty?
561
- op.push("#{dist.call}/*")
619
+ op << "#{dist.call}/*"
562
620
  else
563
- op.map! { |val| basepath(val) }
621
+ op.add_path
564
622
  end
565
- op.append
566
- else
567
- dist.call unless op.arg?(*(flag == :poetry ? ['dist-dir'] : ['d', 'dest']))
568
- op.clear(pass: false)
569
623
  end
570
- run(from: :"#{flag}:publish", interactive: "Publish #{sub_style(project, styles: theme[:active])}")
624
+ run(from: :"#{flag}:publish", interactive: ['Publish', 'N', project])
571
625
  end
572
626
 
573
- def pip(flag, opts = [])
574
- cmd = pip_session flag
575
- 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)
631
+ case flag
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)
576
636
  case flag
577
- when :uninstall
578
- raise_error('no packages listed', hint: flag) if out.empty?
579
- cmd.merge(out)
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
580
655
  when :freeze
581
656
  venv_init
582
- ret = basepath(out.shift || DEP_PYTHON[4])
583
- cmd << '>' << shell_quote(ret)
584
- 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
585
698
  end
586
- run(from: :"pip:#{flag}")
587
- ret
699
+ print_run(op, banner, **kwargs)
700
+ run(sync: sync, banner: banner, from: :"pip:#{flag}").yield_self { |val| ret || val }
588
701
  end
589
702
 
590
703
  def variable_set(key, *val, **, &blk)
@@ -596,21 +709,17 @@ module Squared
596
709
  end
597
710
  case key
598
711
  when :dependfile
599
- if val.first.nil?
600
- super
712
+ req = basepath(*val)
713
+ if (index = DEP_PYTHON.index(req.basename.to_s))
714
+ @dependindex = index
715
+ @dependfile = req
601
716
  else
602
- req = basepath(*val)
603
- if (index = DEP_PYTHON.index(req.basename.to_s))
604
- @dependindex = index
605
- @dependfile = req
606
- else
607
- log.warn "variable_set: @#{key}=#{req} (not supported)"
608
- end
717
+ log.warn "variable_set: @#{key}=#{req} (not supported)"
609
718
  end
610
719
  when :editable
611
720
  editable_set val.first
612
721
  when :venv
613
- @venv = val.empty? || val.first.nil? ? nil : basepath(*val)
722
+ instance_variable_set(:"@#{key}", (basepath(*val) unless val.empty?))
614
723
  else
615
724
  super
616
725
  end
@@ -621,7 +730,7 @@ module Squared
621
730
  end
622
731
 
623
732
  def outdated?
624
- dependtype > 0 && !task_pass?('outdated')
733
+ dependtype > 0
625
734
  end
626
735
 
627
736
  private
@@ -631,18 +740,16 @@ module Squared
631
740
  end
632
741
 
633
742
  def python_session(*cmd, opts: nil)
634
- pre = preopts(quiet: false)
635
- return session('python', *pre, *cmd, path: venv.nil?) unless opts
743
+ return session('python', *preopts(quiet: false), *cmd, path: venv.nil?) unless opts
636
744
 
637
- op = OptionPartition.new(opts, OPT_PYTHON[:common], project: self, single: singleopt(:python))
638
- ret = session('python', *pre, *op.to_a, *cmd, path: venv.nil?)
639
- [ret, op.extras]
745
+ op = OptionPartition.new(opts, OPT_PYTHON[:common], project: self, single: /\A(?:v+|OO)\z/)
746
+ [session('python', *op.to_a, *cmd, path: venv.nil?), op.extras]
640
747
  end
641
748
 
642
749
  def poetry_session(*cmd)
643
- ret = session('poetry', *cmd, *preopts)
644
- option('project', ignore: false) { |val| ret << quote_option('project', basepath(val)) }
645
- ret
750
+ session('poetry', *cmd, *preopts).tap do |ret|
751
+ option('project', ignore: false) { |val| ret << quote_option('project', basepath(val)) }
752
+ end
646
753
  end
647
754
 
648
755
  def pdm_session(*cmd, opts: nil)
@@ -656,19 +763,29 @@ module Squared
656
763
  def create_session(*cmd, name:, common:, opts: nil)
657
764
  return session(name, *preopts, *cmd, path: venv.nil?) unless opts
658
765
 
659
- op = OptionPartition.new(opts, common, project: self, single: singleopt(name.to_sym))
660
- ret = session(name, *preopts, *op.to_a, *cmd, path: venv.nil?)
661
- [ret, op.extras]
766
+ op = OptionPartition.new(opts, common, project: self, single: singleopt)
767
+ [session(name, *op.to_a, *cmd, path: venv.nil?), op.extras]
662
768
  end
663
769
 
664
770
  def append_pip(flag, opts, target: @session, from: nil)
665
- unless from && !opts.empty?
666
- append_global(target: target)
667
- 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')
668
784
  end
669
- 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)
670
786
  append_global(target: target)
671
- if from == :install
787
+ case flag
788
+ when :install, :lock, :wheel
672
789
  edit = nil
673
790
  op.each do |opt|
674
791
  if opt =~ op.values
@@ -683,7 +800,6 @@ module Squared
683
800
  op.found << opt
684
801
  end
685
802
  end
686
- op << '--no-build-isolation' if option('build-isolation', equals: '0')
687
803
  op.swap
688
804
  if edit
689
805
  edit = basepath(edit) unless %r{\A[a-z]+(?:\+[a-z]+)?://}i.match?(edit)
@@ -693,16 +809,8 @@ module Squared
693
809
  op << quote_option('e', edit)
694
810
  end
695
811
  end
696
- case flag
697
- when :editable, :upgrade
698
- op.extras
699
- else
700
- op.clear
701
- []
702
- end
703
- else
704
- op.extras
705
812
  end
813
+ op
706
814
  end
707
815
 
708
816
  def append_editable(target: @session)
@@ -712,29 +820,29 @@ module Squared
712
820
  OptionPartition.delete_key(target, 'e', 'editable')
713
821
  case val
714
822
  when '0', 'false'
715
- return unless installable?
823
+ return
716
824
  else
717
825
  val = basepath val
718
826
  end
719
- elsif session_arg?('e', 'editable', target: target) || !installable?
827
+ elsif session_arg?('e', 'editable', target: target) || !(val = editable)
720
828
  return
721
- else
722
- val = editable
723
829
  end
724
- target << (val ? quote_option('e', basepath(val)) : '.')
830
+ target << quote_option('e', basepath(val))
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 << quote_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,15 +914,15 @@ 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)
815
923
  case flag
816
924
  when :python
817
- /\A(?:v+|q+|b+|V+|O+)\z/
925
+ /\A(?:v+|q+|b+)\z/
818
926
  when :twine
819
927
  nil
820
928
  else
@@ -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
@@ -897,16 +1005,28 @@ module Squared
897
1005
  .clear(pass: false)
898
1006
  .arg?(/\A-v+\z/)
899
1007
  run(op, env, exception: true, banner: banner)
900
- if poetry?
901
- install(:upgrade, ['poetry'])
902
- elsif setuptools?
903
- install(:upgrade, ['setuptools', 'wheel'])
904
- end
905
1008
  puts(dir.directory? ? "Success: #{dir}" : 'Failed') if banner && !status
906
1009
  end
907
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
+
908
1028
  def installable?
909
- setuptools? || !!pyprojectfile
1029
+ setuptools? || !pyprojectfile.nil?
910
1030
  end
911
1031
 
912
1032
  def setuptools?