squared 0.5.4 → 0.5.6

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.
@@ -98,7 +98,7 @@ module Squared
98
98
  'pack' => nil
99
99
  })
100
100
 
101
- def initialize(*, init: nil, **kwargs)
101
+ def initialize(*, init: nil, asdf: 'nodejs', **kwargs)
102
102
  super
103
103
  if @pass.include?(Node.ref)
104
104
  initialize_ref Node.ref
@@ -141,12 +141,12 @@ module Squared
141
141
  depend(:add, packages: packages, save: save, exact: exact)
142
142
  end
143
143
  when 'run'
144
- next if (list = read_scripts).empty?
144
+ next if scripts.empty?
145
145
 
146
146
  format_desc action, nil, "script,opts*|#{indexchar}index+|#,pattern*"
147
147
  task action, [:script] do |_, args|
148
148
  if args.script == '#'
149
- format_list(list, "run[#{indexchar}N]", 'scripts', grep: args.extras, from: dependfile)
149
+ format_list(scripts.to_a, "run[#{indexchar}N]", 'scripts', grep: args.extras, from: dependfile)
150
150
  else
151
151
  args = param_guard(action, 'script', args: args.to_a)
152
152
  opts = []
@@ -767,8 +767,8 @@ module Squared
767
767
  seg[4] &&= seg[4].succ
768
768
  else
769
769
  seg[2] = seg[2].succ
770
+ seg[4] &&= '0'
770
771
  end
771
- seg[4] = '0'
772
772
  when :patch
773
773
  seg[4] &&= seg[4].succ
774
774
  end
@@ -782,15 +782,15 @@ module Squared
782
782
  log.info "bump version #{cur} to #{val} (#{flag})"
783
783
  on :first, :bump
784
784
  end
785
- if verbose
785
+ if stdin?
786
+ puts val
787
+ elsif verbose
786
788
  major = flag == :major
787
789
  emphasize("version: #{val}", title: name, border: borderstyle, sub: [
788
790
  headerstyle,
789
791
  { pat: /\A(version:)( )(\S+)(.*)\z/, styles: color(major ? :green : :yellow), index: 3 },
790
792
  { pat: /\A(version:)(.*)\z/, styles: theme[major ? :major : :active] }
791
793
  ])
792
- elsif stdin?
793
- puts val
794
794
  end
795
795
  on :last, :bump unless dryrun?
796
796
  else
@@ -807,7 +807,7 @@ module Squared
807
807
  cmd = session dependbin, 'pack'
808
808
  if dependtype(:yarn) > 1
809
809
  op = OptionPartition.new(opts, OPT_BERRY[:pack], cmd, project: self)
810
- op << quote_option('out', Pathname.pwd + "#{project}-#{version}.tgz") unless op.arg?('out')
810
+ op.append?('out', Pathname.pwd + "#{project}-#{version}.tgz")
811
811
  else
812
812
  op = OptionPartition.new(opts, pnpm? ? OPT_PNPM[:pack] : OPT_NPM[:pack] + OPT_NPM[:common], cmd,
813
813
  project: self)
@@ -822,7 +822,7 @@ module Squared
822
822
  end
823
823
  end
824
824
  end
825
- op << quote_option('pack-destination', Dir.pwd) unless op.arg?('pack-destination')
825
+ op.append?('pack-destination', Dir.pwd)
826
826
  end
827
827
  op.clear
828
828
  run(from: :pack)
@@ -943,6 +943,10 @@ module Squared
943
943
  read_packagemanager :name
944
944
  end
945
945
 
946
+ def scripts
947
+ @scripts ||= read_packagemanager(:scripts).yield_self { |ret| ret.is_a?(Hash) ? ret : {} }
948
+ end
949
+
946
950
  private
947
951
 
948
952
  def read_packagemanager(key = nil, version: nil, update: false)
@@ -974,13 +978,8 @@ module Squared
974
978
  ret
975
979
  end
976
980
 
977
- def read_scripts
978
- ret = read_packagemanager(:scripts)
979
- ret.is_a?(Hash) ? ret.to_a : []
980
- end
981
-
982
981
  def append_loglevel(target: @session)
983
- level = env 'NODE_LOGLEVEL'
982
+ level = env('NODE_LOGLEVEL')
984
983
  silent = !verbose || level == 'silent'
985
984
  return unless silent || level
986
985
 
@@ -1011,10 +1010,6 @@ module Squared
1011
1010
  end
1012
1011
  end
1013
1012
 
1014
- def dryrun?(prefix = dependbin, **)
1015
- super || !option('dry-run', prefix: prefix).nil?
1016
- end
1017
-
1018
1013
  def dependbin
1019
1014
  if yarn?
1020
1015
  'yarn'
@@ -1039,6 +1034,10 @@ module Squared
1039
1034
  { pat: /^(npm )(.+)$/, styles: :bold }
1040
1035
  ]
1041
1036
  end
1037
+
1038
+ def dryrun?(prefix = dependbin, **)
1039
+ super || !option('dry-run', prefix: prefix).nil?
1040
+ end
1042
1041
  end
1043
1042
 
1044
1043
  Application.implement Node
@@ -30,7 +30,7 @@ module Squared
30
30
  OPT_POETRY = {
31
31
  common: %w[ansi no-ansi no-cache n|no-interaction no-plugins q|quiet v|verbose P|project=p].freeze,
32
32
  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=b r|repository=b
33
+ publish: %w[build dry-run skip-existing cert=p client-cert=p dist-dir=p p|password=b r|repository=q
34
34
  u|username=b].freeze
35
35
  }.freeze
36
36
  OPT_PDM = {
@@ -45,7 +45,7 @@ module Squared
45
45
  q|quiet v|verbose].freeze,
46
46
  build: %w[clean-hooks-after ext hooks-only no-hooks c|clean t|target=b].freeze,
47
47
  publish: %w[initialize-auth n|no-prompt y|yes a|auth=q ca-cert=p client-cert=p client-key=p o|option=q
48
- p|publisher=b r|repo=b u|user=q].freeze
48
+ p|publisher=b r|repo=q u|user=q].freeze
49
49
  }.freeze
50
50
  OPT_TWINE = {
51
51
  publish: %w[attestations disable-progress-bar non-interactive s|sign skip-existing verbose cert=p
@@ -78,7 +78,7 @@ module Squared
78
78
 
79
79
  attr_reader :venv, :editable
80
80
 
81
- def initialize(*, editable: '.', verbose: nil, **kwargs)
81
+ def initialize(*, editable: '.', verbose: nil, asdf: 'python', **kwargs)
82
82
  super
83
83
  if @pass.include?(Python.ref)
84
84
  initialize_ref Python.ref
@@ -132,7 +132,7 @@ module Squared
132
132
  format_desc action, nil, "script+|#{indexchar}index+|#,pattern*"
133
133
  task action, [:command] do |_, args|
134
134
  found = 0
135
- ['tool.poetry.scripts', 'tool.pdm.scripts', 'project.scripts'].each_with_index do |table, index|
135
+ %w[tool.poetry.scripts tool.pdm.scripts project.scripts].each_with_index do |table, index|
136
136
  next if (list = read_pyproject(table)).empty?
137
137
 
138
138
  if args.command == '#'
@@ -276,13 +276,16 @@ module Squared
276
276
  end
277
277
  when :upgrade
278
278
  task flag, [:strategy] do |_, args|
279
- case (strategy = args.strategy)
280
- when 'eager', 'only-if-needed'
281
- args = args.extras
282
- else
283
- args = args.to_a
284
- strategy = nil
285
- end
279
+ args = case (strategy = args.strategy)
280
+ when 'eager', 'only-if-needed'
281
+ args.extras
282
+ when 'needed'
283
+ strategy = 'only-if-needed'
284
+ args.extras
285
+ else
286
+ strategy = nil
287
+ args.to_a
288
+ end
286
289
  install(flag, args, strategy: strategy)
287
290
  end
288
291
  when :target
@@ -320,12 +323,18 @@ module Squared
320
323
  end
321
324
  break unless flag == :python
322
325
  when 'publish'
323
- format_desc(action, flag, 'opts*', after: case flag
324
- when :hatch then 'artifacts?'
325
- when :twine then 'dist?'
326
- end)
326
+ format_desc(action, flag, 'test?,opts*', after: case flag
327
+ when :hatch then 'artifacts?'
328
+ when :twine then 'dist?'
329
+ end)
327
330
  task flag do |_, args|
328
- publish flag, args.to_a
331
+ args = args.to_a
332
+ publish(flag, args, test: if args.first == 'test'
333
+ args.shift
334
+ true
335
+ else
336
+ false
337
+ end)
329
338
  end
330
339
  end
331
340
  end
@@ -532,7 +541,7 @@ module Squared
532
541
  run(from: :"#{flag}:build")
533
542
  end
534
543
 
535
- def publish(flag, opts = [])
544
+ def publish(flag, opts = [], test: false)
536
545
  case flag
537
546
  when :poetry
538
547
  poetry_session 'publish'
@@ -549,10 +558,17 @@ module Squared
549
558
  end
550
559
  op = OptionPartition.new(opts, list, @session, project: self, single: singleopt(flag))
551
560
  dist = lambda do
552
- (path + 'dist').tap do |dir|
561
+ path.join('dist').tap do |dir|
553
562
  raise_error('no source files found', hint: dir) unless dir.directory? && !dir.empty?
554
563
  end
555
564
  end
565
+ if test
566
+ if op.arg?('r', flag == :hatch ? 'repo' : 'repository')
567
+ op.push('test')
568
+ else
569
+ op << quote_option('r', 'testpypi')
570
+ end
571
+ end
556
572
  case flag
557
573
  when :hatch, :twine
558
574
  if op.empty?
@@ -676,6 +692,7 @@ module Squared
676
692
  op.found << opt
677
693
  end
678
694
  end
695
+ op << '--no-build-isolation' if option('build-isolation', equals: '0')
679
696
  op.swap
680
697
  if edit
681
698
  edit = path + edit unless %r{\A[a-z]+(?:\+[a-z]+)?://}i.match?(edit)
@@ -790,7 +807,7 @@ module Squared
790
807
  end
791
808
  @pyproject[table] = ret
792
809
  end
793
- return ret.find { |val| val[0] == key }&.last if key
810
+ return ret.find { |val| val.first == key }&.last if key
794
811
 
795
812
  ret
796
813
  end
@@ -25,8 +25,8 @@ module Squared
25
25
  common: %w[no-color V|verbose retry=i].freeze,
26
26
  install: %w[frozen no-cache no-prune system binstubs=p? path=p standalone=q? target-rbconfig=p trust-policy=b
27
27
  with=q without=q].freeze,
28
- install_base: %w[full-index quiet retry gemfile=p j|jobs=i].freeze,
29
- update: %w[conservative local pre redownload ruby strict bundler=b? g|group=q source=b].freeze,
28
+ install_base: %w[force full-index quiet redownload retry gemfile=p j|jobs=i].freeze,
29
+ update: %w[conservative local pre ruby strict bundler=b? g|group=q source=b].freeze,
30
30
  outdated: %w[filter-major filter-minor filter-patch groups local parseable pre only-explicit strict
31
31
  update-strict g|group=q source=b].freeze,
32
32
  exec: %w[gemfile=p].freeze,
@@ -91,7 +91,7 @@ module Squared
91
91
  'irb' => nil
92
92
  })
93
93
 
94
- def initialize(*, autodetect: false, gemspec: nil, **kwargs)
94
+ def initialize(*, autodetect: false, gemspec: nil, asdf: 'ruby', **kwargs)
95
95
  super
96
96
  if @pass.include?(Ruby.ref)
97
97
  initialize_ref Ruby.ref
@@ -102,7 +102,13 @@ module Squared
102
102
  end
103
103
  dependfile_set GEMFILE
104
104
  @autodetect = autodetect
105
- @gemfile = path + gemspec if gemspec
105
+ @gemfile = if gemspec == false
106
+ false
107
+ elsif gemspec
108
+ path + (gemspec.include?('.') ? gemspec : "#{gemspec}.gemspec")
109
+ elsif (gemspec = path + "#{name}.gemspec").exist? || (gemspec = path + "#{project}.gemspec").exist?
110
+ gemspec
111
+ end
106
112
  return if !@output[0].nil? || !@copy.nil? || version || @autodetect || !rakefile
107
113
 
108
114
  begin
@@ -140,21 +146,21 @@ module Squared
140
146
  format_desc action, nil, "task+,opts*|#{indexchar}index+|#,pattern*"
141
147
  task action, [:command] do |_, args|
142
148
  if args.command == '#'
143
- format_list(read_rakefile, "rake[#{indexchar}N]", 'tasks', grep: args.extras, from: rakefile,
144
- each: ->(val) { val[0] + val[1].to_s })
149
+ format_list(raketasks, "rake[#{indexchar}N]", 'tasks', grep: args.extras, from: rakefile,
150
+ each: ->(val) { val[0] + val[1].to_s })
145
151
  else
146
152
  args, opts = args.to_a.partition { |val| indexitem(val) }
147
153
  if args.empty?
148
154
  rake(opts: opts)
149
155
  else
150
- list = read_rakefile
156
+ tasks = raketasks
151
157
  while (n, pre = indexitem(args.shift))
152
- if (item = list[n - 1])
158
+ if (item = tasks[n - 1])
153
159
  cmd = pre ? "#{pre} #{item.first}" : item.first
154
160
  elsif exception
155
- indexerror n, list
161
+ indexerror n, tasks
156
162
  else
157
- log.warn "rake task #{n} of #{list.size} (out of range)"
163
+ log.warn "rake task #{n} of #{tasks.size} (out of range)"
158
164
  next
159
165
  end
160
166
  if opts.empty?
@@ -177,7 +183,8 @@ module Squared
177
183
  else
178
184
  format_desc(action, nil, 'opts*', before: case action
179
185
  when 'cache', 'check' then nil
180
- else 'command+' end)
186
+ else 'command+'
187
+ end)
181
188
  task action do |_, args|
182
189
  bundle(action, *args.to_a)
183
190
  end
@@ -209,7 +216,7 @@ module Squared
209
216
  when :build, :push, :exec, :update
210
217
  format_desc(action, flag, 'opts*', after: case flag
211
218
  when :exec then 'command,args*'
212
- when :push then 'file?'
219
+ when :push then 'file?|:'
213
220
  when :update then 'name*'
214
221
  end)
215
222
  task flag do |_, args|
@@ -426,14 +433,23 @@ module Squared
426
433
 
427
434
  def install(flag, opts = [])
428
435
  bundle_session 'install', "--#{flag}"
429
- append_bundle opts, OPT_BUNDLE[:install_base] + OPT_BUNDLE[:install] + OPT_BUNDLE[:common]
436
+ op = append_bundle opts, OPT_BUNDLE[:install_base] + OPT_BUNDLE[:install] + OPT_BUNDLE[:common]
437
+ if op.arg?('force')
438
+ op.delete('--force')
439
+ if flag != :redownload
440
+ op << '--redownload'
441
+ elsif (lock = basepath('Gemfile.lock')).exist?
442
+ config = basepath('.bundle', 'config')
443
+ lock.delete unless config.exist? && config.read.match?(/\bBUNDLE_FROZEN:\s+"true"/)
444
+ end
445
+ end
430
446
  run_rb(from: :install)
431
447
  end
432
448
 
433
449
  def update(flag, opts = [])
434
450
  bundle_session 'update', "--#{flag}"
435
451
  append_bundle(opts, OPT_BUNDLE[:install_base] + OPT_BUNDLE[:update] + OPT_BUNDLE[:common],
436
- append: flag == :all ? nil : /\A[a-z\-]+=/)
452
+ append: flag == :all ? nil : /\A[a-z-]+=/)
437
453
  run_rb(from: :update)
438
454
  end
439
455
 
@@ -451,16 +467,44 @@ module Squared
451
467
  when :version
452
468
  pwd_set do
453
469
  out = []
454
- [
470
+ order = { 'rvm' => -1, 'rbenv' => -1, 'chruby' => -1, 'asdf' => -1 }
471
+ ENV.fetch('PATH', '').split(':').each_with_index do |val, index|
472
+ order.each_key do |key|
473
+ if val.match?(%r{[/.]#{key}/})
474
+ order[key] = index
475
+ break
476
+ end
477
+ end
478
+ end
479
+ paths = [
455
480
  '$HOME/.rvm/bin/rvm',
456
481
  '/usr/local/rvm/bin/rvm',
457
482
  '/usr/share/rvm/bin/rvm',
458
483
  "#{ENV.fetch('RBENV_ROOT', '$HOME/.rbenv')}/bin/rbenv",
459
484
  '/usr/bin/rbenv',
460
- '/usr/local/share/chruby/chruby.sh',
461
- "#{ENV.fetch('ASDF_DATA_DIR', '$HOME/.asdf')}/plugins/ruby/bin/install",
462
- ''
463
- ].each do |val|
485
+ '/usr/local/share/chruby/chruby.sh'
486
+ ]
487
+ paths << "#{ENV.fetch('ASDF_DATA_DIR', '$HOME/.asdf')}/installs/#{@asdf.first}" if @asdf
488
+ paths.sort do |a, b|
489
+ c = -1
490
+ d = -1
491
+ order.each do |key, val|
492
+ pat = %r{/\.?#{key}}
493
+ c = val if a.match?(pat)
494
+ d = val if b.match?(pat)
495
+ end
496
+ if c == d
497
+ 0
498
+ elsif c == -1
499
+ 1
500
+ elsif d == -1
501
+ -1
502
+ else
503
+ c < d ? -1 : 1
504
+ end
505
+ end
506
+ .append('')
507
+ .each do |val|
464
508
  next unless val.empty? || File.exist?(val.sub('$HOME', Dir.home))
465
509
 
466
510
  trim = ->(s) { s[/\A\D+\d+\.\d+(?:\.\S+)?/, 0].sub(/\A([a-z]+)-/i, '\1 ') }
@@ -475,12 +519,15 @@ module Squared
475
519
  when 'chruby.sh'
476
520
  chruby = session_output 'source', val
477
521
  `#{chruby.with('ruby --version')}`
478
- when 'install'
479
- ver = '.tool-versions'
480
- `asdf current ruby`[/ruby\s+\S+/, 0].sub(/\s+/, ' ')
481
522
  else
482
- ver = nil
483
- `ruby --version`
523
+ if @asdf
524
+ cmd = 'asdf'
525
+ ver = '.tool-versions'
526
+ `asdf current #{@asdf.first}`[/\A\S+\s+\S+/, 0].sub(/\s+/, ' ')
527
+ else
528
+ ver = nil
529
+ `ruby --version`
530
+ end
484
531
  end)
485
532
  break if workspace.windows?
486
533
 
@@ -488,7 +535,7 @@ module Squared
488
535
  out << trim.call(case cmd
489
536
  when 'chruby.sh'
490
537
  `#{chruby.with('chruby --version')}`.sub(':', '')
491
- when 'install'
538
+ when 'asdf'
492
539
  "asdf #{`asdf version`.delete_prefix('v')}"
493
540
  else
494
541
  `#{cmd} --version`
@@ -500,8 +547,8 @@ module Squared
500
547
  `rbenv which ruby`
501
548
  when 'chruby.sh'
502
549
  `#{chruby.with('which ruby')}`
503
- when 'install'
504
- `asdf which ruby`
550
+ when 'asdf'
551
+ `asdf which #{@asdf.first}`
505
552
  else
506
553
  `which ruby`
507
554
  end)
@@ -692,14 +739,14 @@ module Squared
692
739
  .clear(pass: false)
693
740
  end
694
741
  when :push
695
- if op.empty?
696
- file = path + (if (spec = gemspec)
742
+ if op.empty? || (n = op.index(':'))
743
+ file = path + (if !n && (spec = gemspec)
697
744
  "#{spec.name}-#{spec.version}.gem"
698
745
  else
699
746
  choice_index('Select a file', Dir.glob('*.gem', base: path), force: true)
700
747
  end)
701
748
  else
702
- file = path + op.shift
749
+ file = path + op.shift.yield_self { |val| val.include?('.') ? val : "#{val}.gem" }
703
750
  raise_error('gem not found', hint: file) unless file.exist?
704
751
  raise_error("unknown args: #{op.join(', ')}", hint: flag) unless op.empty?
705
752
  end
@@ -815,14 +862,11 @@ module Squared
815
862
  def gemspec
816
863
  return @gemspec unless @gemspec.nil?
817
864
 
818
- begin
819
- if (file = gemfile)
820
- @gemspec = Gem::Specification.load(file.to_s)
821
- end
822
- rescue StandardError => e
823
- log.debug e
824
- end
825
- @gemspec ||= false
865
+ @gemspec = if (file = gemfile)
866
+ Gem::Specification.load(file.to_s) rescue false
867
+ else
868
+ false
869
+ end
826
870
  end
827
871
 
828
872
  def gemname
@@ -938,6 +982,7 @@ module Squared
938
982
  else
939
983
  op.clear
940
984
  end
985
+ op
941
986
  end
942
987
 
943
988
  def ruby_session(*cmd, **kwargs)
@@ -979,27 +1024,10 @@ module Squared
979
1024
  session_output('rake', *cmd, **kwargs)
980
1025
  end
981
1026
 
982
- def read_rakefile
983
- @read_rakefile ||= [].tap do |ret|
984
- opt = rakepwd
985
- pwd_set(pass: !opt.nil?) do
986
- IO.popen(rake_output(opt, '-AT').to_s).each do |line|
987
- next unless line =~ /^rake ((?:[^\[: ]+:?)+)(\[[^\]]+\])?/
988
-
989
- ret << [$1, $2]
990
- end
991
- end
992
- end
993
- end
994
-
995
1027
  def preopts
996
1028
  verbosetype > 1 && !session_arg?('quiet') ? ['--verbose'] : []
997
1029
  end
998
1030
 
999
- def gemdir?
1000
- !@gemdir.nil? && @gemdir.exist? && !@gemdir.empty?
1001
- end
1002
-
1003
1031
  def variables
1004
1032
  (super + %i[version autodetect]).freeze
1005
1033
  end
@@ -1018,6 +1046,19 @@ module Squared
1018
1046
  quote_option 'C', path
1019
1047
  end
1020
1048
 
1049
+ def raketasks
1050
+ @raketasks ||= [].tap do |ret|
1051
+ opt = rakepwd
1052
+ pwd_set(pass: !opt.nil?) do
1053
+ IO.popen(rake_output(opt, '-AT').to_s).each do |line|
1054
+ next unless line =~ /^rake ((?:[^\[: ]+:?)+)(\[[^\]]+\])?/
1055
+
1056
+ ret << [$1, $2]
1057
+ end
1058
+ end
1059
+ end
1060
+ end
1061
+
1021
1062
  def gempwd
1022
1063
  return unless !pwd? && semgte?(Gem::VERSION, '3.4.2')
1023
1064
 
@@ -1045,6 +1086,10 @@ module Squared
1045
1086
  def gempath(val = version)
1046
1087
  File.join('gems', "#{gemname}-#{val}")
1047
1088
  end
1089
+
1090
+ def gemdir?
1091
+ !@gemdir.nil? && @gemdir.exist? && !@gemdir.empty?
1092
+ end
1048
1093
  end
1049
1094
 
1050
1095
  Application.implement Ruby
@@ -10,15 +10,22 @@ module Squared
10
10
  include Common::Shell
11
11
  extend Forwardable
12
12
 
13
+ OPT_VALUE = /\A([^=]+)=(.+)\z/
14
+ private_constant :OPT_VALUE
15
+
13
16
  class << self
14
17
  include Common::Format
15
18
  include Shell
16
19
  include Prompt
17
20
 
18
- def append(target, *args, delim: false, escape: false, quote: true, **)
21
+ def append(target, *args, delim: false, escape: false, quote: true, strip: nil, **)
19
22
  return if (ret = args.flatten).empty?
20
23
 
21
24
  target << '--' if delim && !target.include?('--')
25
+ if strip
26
+ pat, s = Array(strip)
27
+ ret.map! { |val| val.gsub(pat, s || '') }
28
+ end
22
29
  ret.map! { |val| escape ? shell_escape(val, quote: quote) : shell_quote(val) } if escape || quote
23
30
  if target.is_a?(Set)
24
31
  target.merge(ret)
@@ -74,7 +81,7 @@ module Squared
74
81
  def_delegators :@target, :+, :-, :<<, :any?, :none?, :include?, :add, :add?, :find, :find_all, :find_index,
75
82
  :merge, :delete, :delete?, :delete_if, :grep, :grep_v, :inspect, :to_a, :to_s
76
83
  def_delegators :@extras, :empty?, :each, :each_with_index, :partition, :dup, :first, :last, :shift, :unshift,
77
- :pop, :push, :index, :delete_at, :join, :map, :map!, :select, :reject, :size
84
+ :pop, :push, :concat, :index, :delete_at, :join, :map, :map!, :select, :select!, :reject, :size
78
85
 
79
86
  def_delegator :@extras, :delete, :remove
80
87
  def_delegator :@extras, :delete_if, :remove_if
@@ -164,7 +171,7 @@ module Squared
164
171
  elsif opt.start_with?('no-') && no.include?(name = opt[3..-1])
165
172
  add "--no-#{name}"
166
173
  else
167
- if opt =~ /\A([^=]+)=(.+)\z/
174
+ if opt =~ OPT_VALUE
168
175
  key = $1
169
176
  val = $2
170
177
  merge = m.include?(key)
@@ -209,6 +216,52 @@ module Squared
209
216
  self
210
217
  end
211
218
 
219
+ def values_of(*args, strict: true, first: false, last: false)
220
+ eq, s = strict ? ['=', '[^ ]+'] : ['(?:=| +)', '[^-][^ ]*']
221
+ grp = ["\"((?:[^\"]|(?<=\\\\)\"(?!$#{Rake::Win32.windows? ? '| ' : ''}))*)\""]
222
+ grp << "'((?:[^']|'\\\\'')*)'" unless Rake::Win32.windows?
223
+ grp << "(#{s})"
224
+ args.map! do |opt|
225
+ if opt.size == 1
226
+ /(?:\A| )-#{opt} ?([^ ]+)/
227
+ else
228
+ /(?:\A| )--#{opt + eq}(?:#{grp.join('|')})/
229
+ end
230
+ end
231
+ ret = []
232
+ target.each do |opt|
233
+ args.each do |pat|
234
+ next unless opt =~ pat
235
+
236
+ ret << ($1 || $2 || $3)
237
+ break
238
+ end
239
+ end
240
+ return ret unless first || last
241
+
242
+ if last
243
+ last.is_a?(Numeric) ? ret.last(last) : ret.last
244
+ else
245
+ first.is_a?(Numeric) ? ret.first(first) : ret.first
246
+ end
247
+ end
248
+
249
+ def uniq(list)
250
+ items = map { |val| nameonly(val) }
251
+ list.reject do |val|
252
+ next true if items.include?(s = nameonly(val))
253
+
254
+ pat = /\A#{s = fill_option(s)}(?:#{s.start_with?('--') ? '[= ]' : '.*'}|\z)/
255
+ any? { |opt| opt.match?(pat) }
256
+ end
257
+ end
258
+
259
+ def uniq!(list)
260
+ n = size
261
+ concat uniq(list)
262
+ extras if size > n
263
+ end
264
+
212
265
  def clear(opts = nil, errors: false, **kwargs)
213
266
  styles = project.theme[:inline] if project
214
267
  if !opts
@@ -275,7 +328,7 @@ module Squared
275
328
  unless found.empty?
276
329
  add '--' if delim
277
330
  extras.clear
278
- extras.concat(other)
331
+ concat other
279
332
  if path
280
333
  found.each { |val| add_path(val) }
281
334
  else
@@ -291,9 +344,33 @@ module Squared
291
344
  self
292
345
  end
293
346
 
347
+ def append?(key, val = nil, type: nil, force: false, **kwargs)
348
+ return false unless force || !arg?(key)
349
+
350
+ val = yield self if block_given?
351
+ return false unless val
352
+
353
+ type ||= :quote if kwargs.empty?
354
+ add case type
355
+ when :quote
356
+ quote_option(key, val)
357
+ when :basic
358
+ basic_option(key, val)
359
+ else
360
+ shell_option(key, val, **kwargs)
361
+ end
362
+ true
363
+ end
364
+
294
365
  def arg?(*args, **kwargs)
295
366
  OptionPartition.arg?(target, *args, **kwargs)
296
367
  end
368
+
369
+ private
370
+
371
+ def nameonly(val)
372
+ val[OPT_VALUE, 1] || val
373
+ end
297
374
  end
298
375
 
299
376
  class JoinSet < Set