squared 0.4.3 → 0.4.5

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.
@@ -4,13 +4,15 @@ module Squared
4
4
  module Workspace
5
5
  module Project
6
6
  class Python < Git
7
- REQUIREMENTS = %w[requirements.txt pyproject.toml setup.cfg poetry.lock].freeze
7
+ REQUIREMENTS = %w[requirements.txt poetry.lock pyproject.toml setup.cfg].freeze
8
8
  SETUPTOOLS = %w[setup.py pyproject.toml].freeze
9
9
  DIR_PYTHON = (REQUIREMENTS + SETUPTOOLS).freeze
10
10
  OPT_PYTHON = {
11
11
  common: %w[b B d E h i I O OO P q s S u v x c=q m=b W=b X=q check-hash-based-pycs=b].freeze,
12
12
  build: %w[n|no-isolation s|sdist v|verbose w|wheel x|skip-dependency-check C|config-setting=q installer=b
13
- o|outdir=p].freeze
13
+ o|outdir=p].freeze,
14
+ venv: %w[clear copies symlinks system-site-packages upgrade upgrade-deps without-scm-ignore-files
15
+ without-pip prompt=q].freeze
14
16
  }.freeze
15
17
  OPT_PIP = {
16
18
  common: %w[debug disable-pip-version-check isolated no-cache-dir no-color no-input no-python-version-warning
@@ -22,7 +24,9 @@ module Squared
22
24
  config-settings=q c|constraint=p e|editable=b? extra-index-url=q f|find-links=q global-option=q
23
25
  implementation=b i|index-url=q no-binary=q only-binary=q platform=q prefix=p progress-bar=b
24
26
  python-version=q report=p r|requirement=p root=p root-user-action=b src=p t|target=p
25
- upgrade-strategy=b].freeze
27
+ upgrade-strategy=b].freeze,
28
+ uninstall: %w[break-system-packages y|yes r|requirement=p root-user-action=b].freeze,
29
+ freeze: %w[all exclude-editable l|local user exclude=b path=p r|requirement=p].freeze
26
30
  }.freeze
27
31
  OPT_POETRY = {
28
32
  common: %w[ansi no-ansi no-cache n|no-interaction no-plugins P|project=p q|quiet v|verbose].freeze,
@@ -67,7 +71,9 @@ module Squared
67
71
  end
68
72
  end
69
73
 
70
- def initialize(*, **kwargs)
74
+ attr_reader :venv
75
+
76
+ def initialize(*, venv: nil, **kwargs)
71
77
  super
72
78
  if @pass.include?(Python.ref)
73
79
  initialize_ref Python.ref
@@ -78,13 +84,16 @@ module Squared
78
84
  end
79
85
  @dependindex = REQUIREMENTS.index { |file| basepath(file).exist? }
80
86
  @dependfile = basepath(REQUIREMENTS[@dependindex || 0])
87
+ venv_set venv if venv
81
88
  end
82
89
 
83
- @@tasks[ref] = {
90
+ subtasks({
91
+ 'venv' => %i[create remove show].freeze,
92
+ 'pip' => %i[uninstall freeze].freeze,
84
93
  'install' => %i[user force upgrade target editable].freeze,
85
94
  'build' => %i[python poetry hatch].freeze,
86
95
  'publish' => %i[poetry twine hatch].freeze
87
- }.freeze
96
+ })
88
97
 
89
98
  def ref
90
99
  Python.ref
@@ -101,6 +110,51 @@ module Squared
101
110
  namespace action do
102
111
  flags.each do |flag|
103
112
  case action
113
+ when 'venv'
114
+ if flag == :create
115
+ format_desc action, flag, 'dir,opts*'
116
+ task flag, [:dir] do |_, args|
117
+ dir = param_guard(action, flag, args: args, key: :dir)
118
+ opts = python_session('-m venv', opts: args.extras).last
119
+ opts = option_sanitize(opts, OPT_PYTHON[:venv]).first
120
+ append_value(dir = basepath(dir), delim: true)
121
+ option_clear(opts, pass: false)
122
+ run(exception: true)
123
+ puts(dir.directory? ? "Success: #{dir}" : 'Failed')
124
+ end
125
+ elsif venv
126
+ case flag
127
+ when :remove
128
+ next unless projectpath?(venv)
129
+
130
+ format_desc action, flag, 'c|reate?,d|epend?'
131
+ task flag do |_, args|
132
+ venv.rmtree(verbose: true)
133
+ venv_init if has_value?(%w[c create], args.to_a)
134
+ depend if has_value?(%w[d depend], args.to_a)
135
+ end
136
+ when :show
137
+ format_desc action, flag
138
+ task flag do
139
+ puts venv
140
+ end
141
+ end
142
+ end
143
+ when 'pip'
144
+ case flag
145
+ when :freeze
146
+ format_desc action, flag, 'file?=requirements.txt,opts*'
147
+ task flag do |_, args|
148
+ if (file = pip(flag, args.to_a)) && verbose
149
+ puts File.read(file)
150
+ end
151
+ end
152
+ when :uninstall
153
+ format_desc action, flag, 'package+,opts*'
154
+ task flag do |_, args|
155
+ pip flag, args.to_a
156
+ end
157
+ end
104
158
  when 'install'
105
159
  format_desc(action, flag, 'opts*', before: case flag
106
160
  when :target
@@ -167,23 +221,30 @@ module Squared
167
221
  if @depend && !flag
168
222
  super
169
223
  elsif outdated?
224
+ venv_init
170
225
  workspace.rev_clear name
171
- if !flag && dependtype == 4
172
- cmd = session 'poetry', 'install'
226
+ if !flag && dependtype == 2
227
+ cmd = session 'poetry', 'install', '-n'
228
+ cmd << '--no-ansi' unless verbose
173
229
  cmd << '--no-root' if option('no-root')
174
230
  else
175
231
  cmd = pip_session 'install'
176
- case flag
177
- when :user
178
- cmd << "--#{flag}"
179
- when :target
180
- cmd << quote_option('target', basepath(target))
181
- when :force
182
- cmd << '--force-reinstall'
232
+ if flag
233
+ case flag
234
+ when :user
235
+ cmd << '--user'
236
+ when :target
237
+ cmd << quote_option('target', basepath(target))
238
+ when :force
239
+ cmd << '--force-reinstall'
240
+ end
241
+ append_pip(flag, opts, from: :install)
183
242
  else
243
+ if (val = option('editable', 'e'))
244
+ cmd << quote_option('e', editable(val))
245
+ end
184
246
  append_global
185
247
  end
186
- append_pip(flag, opts, from: :install) if flag
187
248
  if dependtype == 1 && !session_arg?('e', 'editable')
188
249
  cmd << '-r requirements.txt' unless session_arg?('r', 'requirement')
189
250
  elsif !session_arg?('e', 'editable', value: true)
@@ -208,7 +269,7 @@ module Squared
208
269
  pwd_set(from: :outdated) do
209
270
  buffer = []
210
271
  out = ->(val) { sync ? puts(val) : buffer << val }
211
- IO.popen(cmd).each do |line|
272
+ IO.popen(runenv || {}, cmd).each do |line|
212
273
  next if line.match?(/^[\s-]+$/)
213
274
 
214
275
  if start > 0
@@ -272,7 +333,7 @@ module Squared
272
333
  out = append_pip(flag, opts, from: :install)
273
334
  case flag
274
335
  when :editable
275
- cmd << '--editable' << (out.pop || '.')
336
+ cmd << quote_option('e', out.shift || '.')
276
337
  option_clear out
277
338
  when :upgrade
278
339
  cmd << '--upgrade'
@@ -283,24 +344,21 @@ module Squared
283
344
  end
284
345
 
285
346
  def buildx(flag, opts = [])
286
- cmd = session flag
287
347
  out = []
288
348
  srcdir = nil
289
349
  case flag
290
350
  when :python
291
- cmd << shell_option('m', 'build')
292
- list = OPT_PYTHON[:build] + OPT_PYTHON[:common]
351
+ cmd, opts = python_session('-m build', opts: opts)
352
+ list = OPT_PYTHON[:build]
293
353
  when :poetry
294
- cmd << 'build'
354
+ cmd = session 'poetry', 'build'
295
355
  list = OPT_POETRY[:build] + OPT_POETRY[:common]
296
356
  when :hatch
297
- cmd << 'build'
298
- list = OPT_HATCH[:build] + OPT_HATCH[:common]
357
+ cmd, opts = hatch_session('build', opts: opts)
358
+ list = OPT_HATCH[:build]
299
359
  end
300
- option_sanitize(opts, list).first.each do |opt|
301
- if opt =~ /^(v+|q+)$/ || (flag == :python && opt =~ /^(b+)$/)
302
- cmd << "-#{$1}"
303
- elsif !srcdir && basepath(opt).exist? && projectpath?(opt)
360
+ option_sanitize(opts, list, single: flag == :python ? /^(?:v+|q+|b+)$/ : /^(?:v+|q+)$/).first.each do |opt|
361
+ if !srcdir && basepath(opt).exist? && projectpath?(opt)
304
362
  srcdir = opt
305
363
  else
306
364
  out << opt
@@ -331,7 +389,6 @@ module Squared
331
389
 
332
390
  def publish(flag, opts = [])
333
391
  cmd = session flag
334
- out = []
335
392
  case flag
336
393
  when :poetry
337
394
  cmd << 'publish'
@@ -340,16 +397,10 @@ module Squared
340
397
  cmd << 'upload'
341
398
  list = OPT_TWINE[:publish]
342
399
  when :hatch
343
- cmd << 'publish'
344
- list = OPT_HATCH[:publish] + OPT_HATCH[:common]
345
- end
346
- option_sanitize(opts, list).first.each do |opt|
347
- if flag != :twine && opt =~ /^(v+|q+)$/
348
- cmd << "-#{$1}"
349
- else
350
- out << opt
351
- end
400
+ opts = hatch_session('publish', opts: opts).last
401
+ list = OPT_HATCH[:publish]
352
402
  end
403
+ out = option_sanitize(opts, list, single: flag == :twine ? nil : /^(?:v+|q+)$/).first
353
404
  if out.empty?
354
405
  dist = basepath.join('dist')
355
406
  raise_error('no source files given', hint: dist) unless dist.directory? && !dist.empty?
@@ -359,6 +410,23 @@ module Squared
359
410
  run(from: :"#{flag}:publish")
360
411
  end
361
412
 
413
+ def pip(flag, opts = [])
414
+ cmd = pip_session flag
415
+ out = append_pip(nil, opts, from: flag)
416
+ case flag
417
+ when :uninstall
418
+ raise_error('no packages given', subject: name, hint: 'uninstall') if out.empty?
419
+ cmd.merge(out)
420
+ when :freeze
421
+ venv_init
422
+ ret = basepath(out.shift || 'requirements.txt')
423
+ cmd << '>' << shell_quote(ret)
424
+ option_clear out
425
+ end
426
+ run(from: :"pip:#{flag}")
427
+ ret
428
+ end
429
+
362
430
  def variable_set(key, *val, **)
363
431
  case key
364
432
  when :dependfile
@@ -369,6 +437,8 @@ module Squared
369
437
  else
370
438
  log.warn "variable_set: @#{key}=#{req} (not supported)"
371
439
  end
440
+ when :venv
441
+ @venv = val.empty? ? nil : basepath(*val)
372
442
  else
373
443
  super
374
444
  end
@@ -385,63 +455,128 @@ module Squared
385
455
  private
386
456
 
387
457
  def pip_session(*cmd)
388
- session('pip', *cmd)
458
+ session('pip', *cmd, path: venv.nil?)
389
459
  end
390
460
 
391
- def python_session(*cmd)
392
- session('python', *cmd)
461
+ def python_session(*cmd, opts: nil)
462
+ return session('python', *cmd, path: venv.nil?) unless opts
463
+
464
+ out = []
465
+ opts = option_sanitize(opts, OPT_PYTHON[:common], target: out, single: /^v+$/).first
466
+ ret = session('python', *out, *cmd, path: venv.nil?)
467
+ [ret, opts]
393
468
  end
394
469
 
395
- def append_pip(flag, opts, target: @session, from: nil)
396
- append_nocolor(target: target)
397
- return [] unless from && !opts.empty?
470
+ def hatch_session(*cmd, opts: nil)
471
+ return session('hatch', *cmd, path: venv.nil?) unless opts
398
472
 
399
- opts, pat = option_sanitize(opts, OPT_PIP[from] + OPT_PIP[:common], target: target)
400
473
  out = []
401
- edit = nil
402
- opts.each do |opt|
403
- if opt =~ /^(v+|q+)$/
404
- cmd << "-#{$1}"
405
- elsif opt =~ pat
406
- case $1
407
- when 'e', 'editable'
408
- edit = $2
474
+ opts = option_sanitize(opts, OPT_HATCH[:common], target: out, single: /^(?:v+|q+)$/).first
475
+ ret = session('hatch', *out, *cmd, path: venv.nil?)
476
+ [ret, opts]
477
+ end
478
+
479
+ def append_pip(flag, opts, from: nil)
480
+ if !from || opts.empty?
481
+ append_global
482
+ return []
483
+ end
484
+ opts, pat = option_sanitize(opts, OPT_PIP[from] + OPT_PIP[:common], single: /^(?:v+|q+)$/)
485
+ append_global
486
+ if from == :install
487
+ out = []
488
+ edit = nil
489
+ opts.each do |opt|
490
+ if opt =~ pat
491
+ case $1
492
+ when 'e', 'editable'
493
+ edit = $2
494
+ end
495
+ elsif flag == :editable && !edit
496
+ edit = opt
497
+ else
498
+ out << opt
409
499
  end
410
- elsif flag == :editable && !edit
411
- edit = opt
412
- else
413
- out << opt
414
500
  end
415
- end
416
- if edit
417
- edit = basepath(edit) unless %r{^[a-z]+(?:\+[a-z]+)?://}i.match?(edit)
418
- if flag == :editable
419
- out << edit
501
+ if edit
502
+ edit = editable(edit)
503
+ if flag == :editable
504
+ out << edit
505
+ else
506
+ target << quote_option('e', edit)
507
+ end
508
+ end
509
+ case flag
510
+ when :editable, :upgrade
511
+ out
420
512
  else
421
- target << quote_option('editable', edit)
513
+ option_clear out
514
+ []
422
515
  end
423
- end
424
- case flag
425
- when :editable, :upgrade
426
- out
427
516
  else
428
- option_clear(out, target: target)
429
- []
517
+ opts
430
518
  end
431
519
  end
432
520
 
433
- def append_global
521
+ def append_global(target: @session)
434
522
  if (val = option('cache-dir'))
435
- cmd << case val
436
- when '0', 'false'
437
- '--no-cache-dir'
438
- else
439
- quote_option('cache-dir', basepath(val))
440
- end
523
+ target << case val
524
+ when '0', 'false'
525
+ '--no-cache-dir'
526
+ else
527
+ quote_option('cache-dir', basepath(val))
528
+ end
529
+ end
530
+ target << shell_option('proxy', val) if (val = option('proxy'))
531
+ target << quote_option('python', basepath(val)) if (val = option('python'))
532
+ append_nocolor(target: target)
533
+ end
534
+
535
+ def editable(val)
536
+ %r{^[a-z]+(?:\+[a-z]+)?://}i.match?(val) ? val : basepath(val)
537
+ end
538
+
539
+ def variables
540
+ (super + %i[venv]).freeze
541
+ end
542
+
543
+ def runenv
544
+ return unless venv
545
+
546
+ if workspace.windows?
547
+ shell_quote(venvbin.join(workspace.powershell? ? 'Activate.ps1' : 'activate.bat'), option: false)
548
+ else
549
+ { 'VIRTUAL_ENV' => venv.to_s, 'PATH' => "#{venvbin}:#{ENV['PATH']}" }
441
550
  end
442
- cmd << shell_option('proxy', val) if (val = option('proxy'))
443
- cmd << quote_option('python', basepath(val)) if (val = option('python'))
444
- append_nocolor
551
+ end
552
+
553
+ def venvbin
554
+ @venv.join(workspace.windows? ? 'Scripts' : 'bin')
555
+ end
556
+
557
+ def venv_set(val)
558
+ @venv = Pathname.new(val)
559
+ @venv = basepath(@venv) unless @venv.absolute?
560
+ if projectpath?(@venv)
561
+ if @venv.exist?
562
+ log.debug "venv found: #{@venv}"
563
+ elsif @path.directory? && !@path.empty?
564
+ @venv.mkpath
565
+ log.info "venv mkdir: #{@venv}"
566
+ end
567
+ elsif !@venv.directory?
568
+ log.warn "venv invalid: #{@venv}"
569
+ @venv = nil
570
+ end
571
+ end
572
+
573
+ def venv_init
574
+ return if !venv || (venvbin.directory? && !venvbin.empty?)
575
+
576
+ puts log_message(Logger::INFO, venv, subject: 'venv', hint: 'init')
577
+ cmd = python_session '-m venv', shell_option('prompt', name), '--upgrade-deps', shell_quote(venv)
578
+ run(cmd, false, exception: true, banner: false)
579
+ puts log_message(Logger::INFO, venv, subject: 'venv', hint: 'created')
445
580
  end
446
581
  end
447
582