squared 0.9.0.pre2 → 0.9.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c463103c9731435a61c03a50774d321728675919d1299f92179ee3283c43c570
4
- data.tar.gz: 608c26522a45d3a91dad5d3c8f094d9da571f8010a16bccc2bb88dc1a92bd90e
3
+ metadata.gz: ed5b9e99423ac6374d36ad8dffd6a50f2714cef292d6324e358ae19d6a547ed9
4
+ data.tar.gz: 1e6358b439d1defc1f6d9601e915c49b619b00614fc104911ac0554dd61ce6cd
5
5
  SHA512:
6
- metadata.gz: e352c390e9299f58b66066f3bdf28ad24d60496b00155eaf8d1dc26b64311f7fef2203bb861c17a021fecc16f2f09849a40d563179a1591eec8db25f502164d3
7
- data.tar.gz: '0598d79fac00ed9a272582db5b655bd5cf75b5597f15d67ec48dd00b6be990e4ef9b171facf19a4c62c54616187f58c46c0f886853dea75ed4a694198640708f'
6
+ metadata.gz: 0e973fe0eb9137cd8e822b3eb5e28fc8275e69f17414373205944c28350b616cb58dae4fe41eb647ab2a018f5a21a3e7e43854cf0711a4f59c15614948bb328c
7
+ data.tar.gz: '0293c7c12c7c8d66a7783162a4d1fd5b9a0ae87f3476e0c0dae73f9a3d918fcda4d93a31894b327161a6b571be9c4988bcb0fb2abff0c10995fe4a64af4e93de'
data/CHANGELOG.md CHANGED
@@ -1,6 +1,42 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## [0.9.1] - 2026-10-22
4
+
5
+ ### Added
6
+
7
+ - sqd: Option --gemfile for rerouting commands through bundle exec.
8
+ - Python install manager for Windows is supported.
9
+ - sqd: Option --sync for running threads in a series was implemented.
10
+
11
+ ## [0.8.7]
12
+
13
+ ### Added
14
+
15
+ - OptionPartition can be initialized with enum and pattern options.
16
+
17
+ ### Fixed
18
+
19
+ - Python venv creation did not run post-install for package managers.
20
+
21
+ ## [0.7.14]
22
+
23
+ ### Changed
24
+
25
+ - Python program meson prefix options use local path resolution.
26
+ - Bundle exec wrapper can be configured per application.
27
+
28
+ ### Fixed
29
+
30
+ - Bundle exec wrapper did not accept boolean values.
31
+
32
+ ## [0.6.21]
33
+
34
+ ### Fixed
35
+
36
+ - RuboCop did not resolve config to project directory.
37
+ - Python program did not use correct separator for double dash options.
38
+
39
+ ## [0.9.0] - 2026-10-12
4
40
 
5
41
  ### Added
6
42
 
@@ -36,6 +72,7 @@
36
72
  - Ruby command script does not run code until it has passed validation.
37
73
  - Ruby gem io-console is used for default console size.
38
74
  - Node program tsc boolean options support explicit values.
75
+ - Python program meson prefix options use local path resolution.
39
76
 
40
77
  ### Removed
41
78
 
@@ -2197,6 +2234,9 @@
2197
2234
 
2198
2235
  - Changelog was created.
2199
2236
 
2237
+ [0.9.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.9.1
2238
+ [0.9.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.9.0
2239
+ [0.8.7]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.7
2200
2240
  [0.8.6]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.6
2201
2241
  [0.8.5]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.5
2202
2242
  [0.8.4]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.4
@@ -2204,6 +2244,7 @@
2204
2244
  [0.8.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.2
2205
2245
  [0.8.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.1
2206
2246
  [0.8.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.0
2247
+ [0.7.14]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.14
2207
2248
  [0.7.13]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.13
2208
2249
  [0.7.12]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.12
2209
2250
  [0.7.11]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.11
@@ -2218,6 +2259,7 @@
2218
2259
  [0.7.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.2
2219
2260
  [0.7.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.1
2220
2261
  [0.7.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.0
2262
+ [0.6.21]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.21
2221
2263
  [0.6.20]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.20
2222
2264
  [0.6.19]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.19
2223
2265
  [0.6.18]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.18
data/README.md CHANGED
@@ -43,12 +43,15 @@ Usage: sqd [options] [rake options] [global...] [:targets...] -- [rake targets..
43
43
 
44
44
  -s, --[no-]strict Do not run commands with invalid arguments
45
45
  --update Use update strategy for applicable commands
46
+ -u, --parallel STR,PAT Run standard tasks with multi-task threads
47
+ -U, --sync STR,PAT Run parallel tasks in a series sequentially
46
48
  -F, --force [STR,STR] Enable force through ENV by application
47
49
  -y, --yes [STR,STR] Enable auto-approve through ENV by application
48
50
  -d, --dry-run Run commands without any modifying actions
49
51
 
50
52
  -S, --select PAGE[:CHOICE] Prompt menu selection pagination display (e.g. 15:75)
51
53
 
54
+ -g, --gemfile Execute commands using bundle exec and nearest Gemfile
52
55
  -p, --pipe 0|1|2 Set file descriptor for communication
53
56
  -o, --pipe-out FILE Write command data stream to a file
54
57
  --fail 0..4 Set application exception level
@@ -101,11 +104,11 @@ Workspace::Application
101
104
  .clean("rake clean", group: "default")
102
105
  .clean(["build/"], group: "app")
103
106
  .log({ file: "tmp/%Y-%m-%d.log", level: "debug" }, group: "app")
104
- .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", group: "default", env: { # Ruby (with C extensions)
107
+ .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", exec: true, group: "default", env: { # Ruby (with C extensions)
105
108
  "CFLAGS" => "-fPIC -O1"
106
109
  })
107
- .add("optparse", doc: "rake rdoc", gemspec: "optparse.gemspec", group: "default") # Uses bundler/gem_tasks (without C extensions)
108
- .add("logger", copy: { from: "lib", glob: "**/*.rb", into: "/home/user/.local/gem/ruby/4.0.0/gems/logger-1.7.0" }, clean: ["tmp/"]) do # autodetect: true | "rvm" | "rbenv" | "asdf" | "mise" | bundler"
110
+ .add("optparse", doc: "rake rdoc", gemspec: "optparse.gemspec", exec: { rake: true, ruby: '../Gemfile', rbs: false }, group: "default") # Uses bundler/gem_tasks (without C extensions)
111
+ .add("logger", copy: { from: "lib", glob: "**/*.rb", into: "/home/user/.local/gem/ruby/4.0.0/gems/logger-1.7.0" }, clean: ["tmp/"]) do # autodetect: true | "rvm" | "rbenv" | "asdf" | "mise" | bundler"
109
112
  self.gemdir = "/home/user/.local/gem/ruby/4.0.0/gems/logger-1.7.0" # Default for "into"
110
113
  end
111
114
  .add("e-mc", "emc", copy: { from: "publish", scope: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node)
data/exe/sqd CHANGED
@@ -45,6 +45,19 @@ class SqdParser < OptionParser
45
45
  end
46
46
  end
47
47
 
48
+ def parse_series(list, session:, flag:)
49
+ pattern, parallel = list.partition do |val|
50
+ next false if val.match?(%r{^[\w~`!@#%&\-=:;"'<>/]+$})
51
+
52
+ Regexp.new(val)
53
+ rescue RegexpError
54
+ false
55
+ end
56
+ s = flag == :sync ? '!' : ''
57
+ (session[:APP][:"build_pattern#{s}"] ||= []).concat(pattern.map { |pat| Regexp.new(pat) }) unless pattern.empty?
58
+ (session[:APP][:"build_parallel#{s}"] ||= []).concat(parallel) unless parallel.empty?
59
+ end
60
+
48
61
  def banner_set(val, session:)
49
62
  session[:BANNER] = val
50
63
  ENV['BANNER'] = val ? 'true' : 'false'
@@ -118,6 +131,7 @@ end
118
131
 
119
132
  SqdParser.new do |sqd|
120
133
  session = Squared::Common::ARG
134
+ argv = ARGV.dup
121
135
  sqd.banner = 'Usage: sqd [options] [rake options] [global...] [:targets...] -- [rake targets...]'
122
136
  sqd.separator ''
123
137
  sqd.on('-R', '--ref STR,STR', Array, 'Run global commands by class reference') do |val|
@@ -144,6 +158,12 @@ SqdParser.new do |sqd|
144
158
  sqd.on('--update', 'Use update strategy for applicable commands') do
145
159
  ENV['UPDATE'] = 'true'
146
160
  end
161
+ sqd.on('-u', '--parallel STR,PAT', Array, 'Run standard tasks with multi-task threads') do |val|
162
+ sqd.parse_series(val, session: session, flag: :parallel)
163
+ end
164
+ sqd.on('-U', '--sync STR,PAT', Array, 'Run parallel tasks in a series sequentially') do |val|
165
+ sqd.parse_series(val, session: session, flag: :sync)
166
+ end
147
167
  sqd.on('-F', '--force [STR,STR]', Array, 'Enable force through ENV by application') do |val|
148
168
  sqd.parse_common(val, session: session, arg: :FORCE)
149
169
  end
@@ -162,6 +182,24 @@ SqdParser.new do |sqd|
162
182
  ENV['SELECT'] = "#{page}:#{choice}"
163
183
  end
164
184
  sqd.separator ''
185
+ sqd.on('-g', '--gemfile', 'Execute commands using bundle exec and nearest Gemfile') do
186
+ require 'pathname'
187
+ next if ENV['RUBYOPT']&.match?(/(^|\b)-r.+#{File::SEPARATOR}bundler#{File::SEPARATOR}setup\b/)
188
+
189
+ gemfile = Pathname.pwd.ascend { |path| break path if path.join('Gemfile').file? } || ENV['BUNDLE_GEMFILE']
190
+ next unless gemfile && (argv.delete('--gemfile') || argv.delete('-g'))
191
+
192
+ include Squared::Common
193
+ include Shell
194
+
195
+ argv.unshift('sqd')
196
+ if gemfile.is_a?(String)
197
+ argv.unshift("--gemfile=#{shell_escape(Pathname.new(gemfile).relative_path_from(Dir.pwd), quote: true)}")
198
+ gemfile = Dir.pwd
199
+ end
200
+ ENV['BUNDLE_GEMFILE'] = nil
201
+ exec(shell_bin('bundle'), 'exec', *argv, chdir: gemfile)
202
+ end
165
203
  sqd.on('-p', '--pipe 0|1|2', Integer, 'Set file descriptor for communication') do |val|
166
204
  sqd.error_exit('out of range', val) unless val.between?(0, 2)
167
205
  session[:PIPE] = val
@@ -211,9 +249,14 @@ SqdParser.new do |sqd|
211
249
  end
212
250
  sqd.on('--version', 'Print the current version') do
213
251
  puts Squared::VERSION
214
- exit 0
252
+ exit
215
253
  end
216
254
  sqd.on('-h', '--help', 'Print the command line options') do
217
- sqd.help_exit
255
+ if defined?(sqd.help_exit)
256
+ sqd.help_exit
257
+ else
258
+ puts sqd.help
259
+ exit
260
+ end
218
261
  end
219
262
  end.run
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.9.0.pre2'
4
+ VERSION = '0.9.1'
5
5
  end
@@ -163,8 +163,14 @@ module Squared
163
163
  end
164
164
  @pass.items.concat(kwargs[:pass].map { |val| val.is_a?(Regexp) ? val : val.to_s }) if kwargs[:pass]
165
165
  @series = Application.register(self, @base.to_a, exclude: kwargs.fetch(:exclude, []))
166
- if (n = ARG[:APP][:series_ref_min])
167
- @series.ref_min = n
166
+ app = ARG[:APP]
167
+ @series.ref_min = app[:series_ref_min] if app.key?(:series_ref_min)
168
+ kwargs[:pattern].concat(app[:build_pattern]) if app.key?(:build_pattern)
169
+ kwargs[:parallel].concat(app[:build_parallel]) if app.key?(:build_parallel)
170
+ kwargs[:parallel].reject! { |val| app[:build_parallel!].include?(val) } if app.key?(:build_parallel!)
171
+ app[:build_pattern!]&.each do |pat|
172
+ kwargs[:pattern].delete(pat)
173
+ kwargs[:parallel].reject! { |val| pat.match?(val) }
168
174
  end
169
175
  self.closed = 'begin'
170
176
  pb = @stage.key?('project:begin')
@@ -652,16 +658,24 @@ module Squared
652
658
  val = val.split(':') if val.is_a?(String)
653
659
  if before || after || opts
654
660
  pos = []
655
- pos << (before.is_a?(Array) ? before.join(',') : before) if before
656
- if opts
657
- pos << if opts.is_a?(Array)
658
- arg ? "#{arg}=#{opts.join(',')}" : opts.join('|')
661
+ append = lambda do |data|
662
+ pos << case data
663
+ when Array
664
+ data.join(',')
665
+ when Hash
666
+ data.keys.join('|')
659
667
  else
660
- opts
668
+ data
661
669
  end
662
670
  end
663
- pos << (after.is_a?(Array) ? after.join(',') : after) if after
664
- val << "#{val.pop}[#{pos.join(',')}]"
671
+ append.call(before)
672
+ pos << if opts.is_a?(Array)
673
+ arg ? "#{arg}=#{opts.join(',')}" : opts.join('|')
674
+ else
675
+ opts
676
+ end
677
+ append.call(after)
678
+ val << "#{val.pop}[#{pos.compact.join(',')}]"
665
679
  end
666
680
  out ? message(*val) : task_desc(*val)
667
681
  end
@@ -717,12 +731,12 @@ module Squared
717
731
  !@extensions.empty? || any? { |proj| proj.enabled?(base: false) }
718
732
  end
719
733
 
720
- def task_base?(key)
721
- series.base?(key)
734
+ def task_base?(...)
735
+ series.base?(...)
722
736
  end
723
737
 
724
- def task_extend?(obj, key)
725
- series.extend?(obj, key)
738
+ def task_extend?(...)
739
+ series.extend?(...)
726
740
  end
727
741
 
728
742
  def task_include?(obj, key, ref: nil)
@@ -758,12 +772,12 @@ module Squared
758
772
  !!find(home)&.enabled?
759
773
  end
760
774
 
761
- def rootpath(*args)
762
- root.join(*args)
775
+ def rootpath(...)
776
+ root.join(...)
763
777
  end
764
778
 
765
- def homepath(*args)
766
- home.join(*args)
779
+ def homepath(...)
780
+ home.join(...)
767
781
  end
768
782
 
769
783
  def pwd
@@ -1139,8 +1139,8 @@ module Squared
1139
1139
  @ref.reverse_each(&blk)
1140
1140
  end
1141
1141
 
1142
- def basepath(*args)
1143
- path.join(*args)
1142
+ def basepath(...)
1143
+ path.join(...)
1144
1144
  end
1145
1145
 
1146
1146
  def basepath!(*args, type: nil)
@@ -182,7 +182,7 @@ module Squared
182
182
  if flags.nil?
183
183
  case action
184
184
  when 'ls'
185
- format_desc(action, nil, VAL_DOCKER[:ls].keys, after: 'a/ll?,s/tandard?,range*', arg: nil)
185
+ format_desc(action, nil, 'a/ll?,s/tandard?,range*', before: VAL_DOCKER[:ls])
186
186
  task action, [:command] do |_, args|
187
187
  command = param_guard(action, 'command', args: args, key: :command)
188
188
  args = args.extras
@@ -1077,7 +1077,7 @@ module Squared
1077
1077
  git(:remote, args.extras, extras: [name, url])
1078
1078
  end
1079
1079
  else
1080
- format_desc action, flag, "#{OPT_GIT[:remote].keys.join('|')},opts*" if TASK_METADATA
1080
+ format_desc(action, flag, 'opts*', before: OPT_GIT[:remote])
1081
1081
  task flag, [:command] do |_, args|
1082
1082
  command = param_guard(action, flag, args: args, key: :command)
1083
1083
  cmd, opts = git_session('remote', opts: args.extras)
@@ -1840,7 +1840,7 @@ module Squared
1840
1840
  if adding.empty? && !cached.empty? && banner?
1841
1841
  puts(cached.lines(chomp: true).map { |val| "cached #{shell_quote(val)}" })
1842
1842
  end
1843
- ENV['COMMIT_PUSH'] = git_spawn 'rev-parse --verify HEAD' if source(co) && source(pu) && !dryrun?
1843
+ ENV['COMMIT_PUSH'] = git_value 'rev-parse --verify HEAD' if source(co) && source(pu) && !dryrun?
1844
1844
  else
1845
1845
  if banner?
1846
1846
  puts 'Nothing to commit'
@@ -306,13 +306,11 @@ module Squared
306
306
  format_desc action, nil, 'version|pre?{major,minor,patch}|prerelease'
307
307
  task action, [:version] do |_, args|
308
308
  version = param_guard(action, 'to', args: args, key: :version)
309
- if version.match?(/^((pre)?(major|minor|patch))|prerelease$/)
310
- flag = version.to_sym
311
- version = nil
309
+ if version.match?(/^(pre)?(major|minor|patch)|prerelease$/)
310
+ bump version.to_sym
312
311
  else
313
- flag = :version
312
+ bump :version, version
314
313
  end
315
- bump flag, version
316
314
  end
317
315
  when 'pack'
318
316
  format_desc action, nil, 'opts*'
@@ -7,16 +7,18 @@ module Squared
7
7
  DEP_PYTHON = %w[poetry.lock pdm.lock pylock.toml setup.cfg pyproject.toml setup.py requirements.txt].freeze
8
8
  DIR_PYTHON = (DEP_PYTHON + %w[meson.build README.rst]).freeze
9
9
  OPT_PYTHON = {
10
- common: %w[b=+ B d E h i I O P q=+ s S u v=+ V=+ x c=q m=b W=b X=q check-hash-based-pycs=b].freeze,
10
+ common: %w[b=+ B d E h i I O P q=+ s S u v=+ V=+ x c=q m=b W=b X=q
11
+ check-hash-based-pycs={always,default,never}].freeze,
11
12
  build: %w[C=bm n|no-isolation s|sdist x|skip-dependency-check v|verbose w|wheel config-json=q config-setting=q
12
13
  installer=b o|outdir=p].freeze,
13
14
  venv: %w[clear copies symlinks system-site-packages upgrade upgrade-deps without-scm-ignore-files without-pip
14
15
  prompt=q].freeze,
15
16
  virtualenv: %w[always-copy clear copies download never-download no-download no-periodic-update no-pip no-seed
16
- no-setuptools no-vcs-ignore q|quiet read-only-app-data reset-app-data symlink-app-data symlinks
17
- system-site-packages with-traceback without-pip upgrade-embed-wheels v|verbose=+ activators=q
18
- app-data=p creator=b discovery=b extra-search-dir=p pip=b prompt=q p|python=q seeder=b
19
- setuptools=b try-first-with=q].freeze
17
+ no-setuptools no-vcs-ignore no-venv-redirect q|quiet read-only-app-data reset-app-data
18
+ symlink-app-data symlinks system-site-packages with-traceback without-pip upgrade-embed-wheels
19
+ venv-redirect v|verbose=+ activators={bash,batch,cshell,fish,nushell,powershell,python,xonsh}
20
+ app-data=p creator=b discovery={builtin} extra-search-dir=p pip=b prompt=q p|python=q
21
+ seeder={app-data,pip} setuptools=b try-first-with=q].freeze
20
22
  }.freeze
21
23
  OPT_PIP = {
22
24
  common: %w[debug disable-pip-version-check isolated no-cache-dir no-color no-input no-proxy-env
@@ -49,27 +51,34 @@ module Squared
49
51
  uninstall: %w[break-system-packages y|yes r|requirement=p root-user-action=b].freeze,
50
52
  wheel: %w[no-verify w|wheel-dir=p].freeze
51
53
  }.freeze
54
+ OPT_PY = {
55
+ common: %w[q|quiet=+ v|verbose=+ y|yes config=p].freeze,
56
+ exec: %w[3=/^\.\d+(\.\d+)?$/m? V=/^:./m?].freeze,
57
+ install: %w[by-id configure dry-run f|force refresh u|update d|download=p s|source=q t|target=p].freeze,
58
+ list: %w[1|one online only-managed format={table,json,jsonl,csv,exe,prefix,url,formats} s|source=q].freeze,
59
+ uninstall: %w[by-id cleanup purge].freeze
60
+ }.freeze
52
61
  OPT_POETRY = {
53
62
  common: %w[ansi no-ansi no-cache n|no-interaction no-plugins q|quiet v|verbose=+ P|project=p].freeze,
54
63
  install: %w[all-extras all-groups compile dry-run no-directory no-root only-root E|extras=q only=q with=q
55
64
  without=q].freeze,
56
- build: %w[clean config-settings=qq f|format=b o|output=p].freeze,
65
+ build: %w[clean c|config-settings=qq f|format=b o|output=p].freeze,
57
66
  publish: %w[build dry-run skip-existing cert=p client-cert=p dist-dir=p p|password=q r|repository=q
58
67
  u|username=qq].freeze
59
68
  }.freeze
60
69
  OPT_PDM = {
61
- common: %w[c|config I|ignore-python no-cache n|non-interactive].freeze,
62
- build: %w[C=bm no-clean no-isolation no-sdist no-wheel q|quiet v|verbose=+ config-setting=qq d|dest=p
63
- p|project=p k|skip=b].freeze,
64
- publish: %w[no-build no-very-ssl q|quiet S|sign skip-existing v|verbose=+ ca-certs=p c|comment=q d|dest=p
65
- i|identity=b P|password=q p|project=p r|repository=q k|skip=b u|username=qq].freeze
70
+ common: %w[c|config I|ignore-python no-cache n|non-interactive q|quiet v|verbose=+ p|project=p
71
+ k|skip=b].freeze,
72
+ build: %w[C=bm no-clean no-isolation no-sdist no-wheel config-setting=qq d|dest=p].freeze,
73
+ publish: %w[no-build no-verify-ssl no-very-ssl S|sign skip-existing ca-certs=p c|comment=q d|dest=p
74
+ i|identity=b P|password=q r|repository=q u|username=qq].freeze
66
75
  }.freeze
67
76
  OPT_HATCH = {
68
77
  common: %w[color interactive no-color no-interactive cache-dir=p config=p data-dir=p e|env=b p|project=b
69
78
  q|quiet=+ v|verbose=+].freeze,
70
- build: %w[clean-hooks-after ext hooks-only no-hooks c|clean t|target=b].freeze,
79
+ build: %w[a|all clean-hooks-after ext hooks-only no-hooks c|clean t|target=b].freeze,
71
80
  publish: %w[initialize-auth n|no-prompt y|yes a|auth=q ca-cert=p client-cert=p client-key=p o|option=q
72
- p|publisher=b r|repo=q u|user=q].freeze
81
+ p|publisher=b r|repo=q u|user=qq].freeze
73
82
  }.freeze
74
83
  OPT_TWINE = {
75
84
  publish: %w[attestations disable-progress-bar non-interactive s|sign skip-existing verbose cert=p
@@ -77,15 +86,20 @@ module Squared
77
86
  sign-with=b u|username=qq].freeze
78
87
  }.freeze
79
88
  OPT_MESON = {
80
- build: %w[D=q clearcache debug errorlogs fatal-meson-warnings pkgconfig.relocatable prefer-static
81
- python.allow-limited-api reconfigure stdsplit strip vsenv werror wipe auto-features=b backend=b
82
- bindir=p build.cmake-prefix-path=p build.pkg-config-path=p buildtype=b cmake-prefix-path=p
83
- cross-file=p datadir=p default-library=b default-both-libraries=b force-fallback-for=q genvslite=b
84
- includedir=p infodir=p install-umask=b layout=b libdir=p libexecdir=p licensedir=p localedir=p
85
- localstatedir=p mandir=p native-file=p optimization=b pkg-config-path=p prefix=p
86
- python.bytecompile=i python.platlibdir=p python.purelibdir=p python.install-env=b sbindir=p
87
- sharedstatedir=p sysconfdir=p unity=b unity-size=i warnlevel=b wrap-mode=b].freeze,
88
- compile: %w[clean v|verbose j|jobs=i l|load-average=b ninja-args=q vs-args=q xcode-args=q].freeze
89
+ build: %w[D=q clearcache debug errorlogs fatal-meson-warnings os2-emxomf pkgconfig.relocatable prefer-static
90
+ python.allow-limited-api reconfigure stdsplit strip vsenv werror wipe auto-features=b
91
+ backend={ninja,vs,vs2010,vs2012,vs2013,vs2015,vs2017,vs2019,vs2022,vs2026,xcode,none}
92
+ bindir=q build.cmake-prefix-path=p build.pkg-config-path=p
93
+ buildtype={plain,debug,debugoptimized,release,minsize,custom} cmake-prefix-path=p cross-file=p
94
+ datadir=q default-library={shared,static,both} default-both-libraries={shared,static,auto}
95
+ force-fallback-for=q genvslite={vs2022,vs2026} includedir=q infodir=q install-umask=b
96
+ layout={mirror,flat} libdir=q libexecdir=q licensedir=q localedir=q localstatedir=q mandir=q
97
+ namingscheme={platform,classic} native-file=p optimization={plain,0,g,1,2,3,s} pkg-config-path=p
98
+ prefix=p python.build-config=p python.bytecompile=b python.platlibdir=p python.purelibdir=p
99
+ python.install-env={auto,prefix,system,venv} sbindir=q sharedstatedir=q sysconfdir=q
100
+ unity={on,off,subprojects} unity-size=i warnlevel={0,1,2,3,everything}
101
+ wrap-mode={default,nofallback,nodownload,forcefallback,nopromote}].freeze,
102
+ compile: %w[C=p clean v|verbose j|jobs=i l|load-average=b ninja-args=q vs-args=q xcode-args=q].freeze
89
103
  }.freeze
90
104
  PASS_PYTHON = {
91
105
  python: %w[c v V].freeze,
@@ -94,7 +108,8 @@ module Squared
94
108
  install: %w[C config-settings c constraint extra-index-url no-binary only-binary platform
95
109
  r requirement].freeze,
96
110
  list: %w[exclude extra-index-url no-binary only-binary].freeze
97
- }.freeze
111
+ }.freeze,
112
+ py: %w[q v quiet verbose].freeze
98
113
  }.freeze
99
114
  private_constant :DEP_PYTHON, :DIR_PYTHON, :OPT_PYTHON, :OPT_PIP, :OPT_POETRY, :OPT_PDM, :OPT_HATCH, :OPT_TWINE,
100
115
  :PASS_PYTHON
@@ -144,6 +159,7 @@ module Squared
144
159
  'outdated' => %i[major minor patch].freeze,
145
160
  'build' => %i[poetry pdm hatch meson python].freeze,
146
161
  'publish' => %i[twine poetry pdm hatch].freeze,
162
+ 'py' => %i[exec install list uninstall].freeze,
147
163
  'run' => nil,
148
164
  'exec' => nil
149
165
  })
@@ -276,7 +292,7 @@ module Squared
276
292
  args = args.to_a
277
293
  if args.empty?
278
294
  args = readline('Enter command', force: true).split(' ', 2)
279
- elsif args.size == 1 && option('interactive', notequals: '0', prefix: ref)
295
+ elsif args.size == 1 && option('interactive', notequals: '0', prefix: 'python')
280
296
  args << readline('Enter arguments', force: false) unless args.first.include?(' ')
281
297
  end
282
298
  venv_init
@@ -346,18 +362,18 @@ module Squared
346
362
  case flag
347
363
  when :upgrade
348
364
  task flag, [:strategy] do |_, args|
349
- install flag, (case args.strategy
350
- when 'eager'
351
- 'eager'
352
- when /^only-if|needed$/
353
- 'only-if-needed'
354
- end.then do |val|
355
- if val
356
- args.extras << "upgrade-strategy=#{val}"
357
- else
358
- args.to_a
359
- end
360
- end)
365
+ val = case args.strategy
366
+ when 'eager'
367
+ 'eager'
368
+ when /^only-if/, /needed$/
369
+ 'only-if-needed'
370
+ end
371
+ args = if val
372
+ args.extras << "upgrade-strategy=#{val}"
373
+ else
374
+ args.to_a
375
+ end
376
+ install flag, args
361
377
  end
362
378
  when :requirement, :target
363
379
  task flag, [:path] do |_, args|
@@ -414,6 +430,26 @@ module Squared
414
430
  end)
415
431
  end
416
432
  break if be
433
+ when 'py'
434
+ break unless windows?
435
+
436
+ case flag
437
+ when :exec
438
+ format_desc action, flag, 'opts*'
439
+ task flag do |_, args|
440
+ opts = args.to_a
441
+ if (n = opts.index { |val| val.start_with?('3.') }) && (opts[n] =~ SEM_VER)
442
+ opts[n] = $3.to_i >= 5 ? "3=.#{$3}.#{$5 || '0'}" : '3'
443
+ end
444
+ command = ENV['PY_EXEC'] || readline('Enter command', force: true)
445
+ py(flag, command, opts: opts)
446
+ end
447
+ else
448
+ format_desc(action, flag, 'opts*', after: flag == :list ? 'filter*' : 'tag*')
449
+ task flag do |_, args|
450
+ py(flag, opts: args.to_a, banner: true)
451
+ end
452
+ end
417
453
  end
418
454
  end
419
455
  end
@@ -746,10 +782,17 @@ module Squared
746
782
  run(from: symjoin(flag, 'publish'), interactive: ['Publish', 'N', project])
747
783
  end
748
784
 
749
- def python(*args, sync: true, banner: verbose?, with: nil, pass: PASS_PYTHON[:python], **kwargs)
750
- op = OptionPartition.new(session_opts(with, args: args, kwargs: kwargs, pass: pass), OPT_PYTHON[:common],
751
- session('python', path: !venv?),
752
- project: self, strict: strict?, auto: %w[help V|version],
785
+ def python(*args, sync: true, banner: verbose?, with: nil, pass: PASS_PYTHON[:python], from: :python, **kwargs)
786
+ py = from == :py && windows?
787
+ list = OPT_PYTHON[:common]
788
+ if py
789
+ list += OPT_PY[:exec]
790
+ list.delete('V=+')
791
+ from = :'py:exec'
792
+ end
793
+ op = OptionPartition.new(session_opts(with, args: args, kwargs: kwargs, pass: pass), list,
794
+ py ? session('py', 'exec') : session('python', path: !venv?),
795
+ project: self, strict: strict?, auto: %w[help V|version], sep: ' ',
753
796
  multiple: [/^-c/], single: singleopt(:python), args: true, stdin: true)
754
797
  op.concat(args)
755
798
  if op.include?('-')
@@ -763,8 +806,14 @@ module Squared
763
806
  op.append(escape: kwargs.fetch(:escape, false), quote: kwargs.fetch(:quote, false))
764
807
  end
765
808
  end
809
+ var = kwargs[:env]
810
+ if var == true
811
+ var = nil
812
+ elsif py && var.nil?
813
+ var = false
814
+ end
766
815
  print_run(op, banner, **kwargs) unless op.stdout
767
- run(sync: sync, banner: op.style?(banner), exception: exception?(**kwargs), from: :python)
816
+ run(op, var, sync: sync, banner: op.style?(banner), exception: exception?(**kwargs), from: from)
768
817
  end
769
818
 
770
819
  def pip(flag, *args, sync: true, banner: verbose?, with: nil, pass: nil, **kwargs)
@@ -850,6 +899,28 @@ module Squared
850
899
  .then { |val| ret || val }
851
900
  end
852
901
 
902
+ def py(flag, *args, sync: true, banner: verbose?, with: nil, pass: nil, **kwargs)
903
+ flag = flag.to_sym
904
+ if flag == :exec
905
+ pass = PASS_PYTHON[:python] if pass.nil?
906
+ python(*args, sync: sync, banner: banner, with: with, pass: pass, from: :py, **kwargs)
907
+ else
908
+ pass = PASS_PYTHON[:py] if pass.nil?
909
+ opts = session_opts(with, args: args, kwargs: kwargs, pass: pass)
910
+ list = OPT_PY[:common] + OPT_PY[flag]
911
+ op = OptionPartition.new(opts, list, session('py', flag), project: self, strict: strict?,
912
+ single: singleopt(:py))
913
+ case flag
914
+ when :install, :uninstall
915
+ raise ArgumentError, message('no tags listed', hint: flag) if op.empty?
916
+ end
917
+ op << '-y' if option('y')
918
+ op.append
919
+ print_run(op, banner, **kwargs)
920
+ run(sync: sync, banner: banner, exception: exception?(**kwargs), from: symjoin('py', flag))
921
+ end
922
+ end
923
+
853
924
  def serve(root, opts = [], bind: 'localhost', port: nil, **)
854
925
  shell(python_session('-m http.server', port, basic_option('b', bind), opts: opts).first.done, chdir: root)
855
926
  end
@@ -951,7 +1022,8 @@ module Squared
951
1022
  pre = preopts(quiet: quiet)
952
1023
  return session(name, *pre, *cmd, path: !venv?) unless opts
953
1024
 
954
- op = OptionPartition.new(opts, common, project: self, strict: strict?, single: singleopt(name.to_sym))
1025
+ op = OptionPartition.new(opts, common, project: self, strict: strict?, sep: name == 'python' ? ' ' : '=',
1026
+ single: singleopt(name.to_sym))
955
1027
  [session(name, *pre, *op.to_a, *cmd, path: !venv?), op.extras]
956
1028
  end
957
1029
 
@@ -1126,6 +1198,8 @@ module Squared
1126
1198
  when :pdm, :poetry
1127
1199
  /\Av+\z/
1128
1200
  when :twine, :meson then nil
1201
+ when :py
1202
+ /\A(vv|qq)\z/
1129
1203
  else
1130
1204
  /\A(v+|q+)\z/
1131
1205
  end
@@ -1230,6 +1304,7 @@ module Squared
1230
1304
  status = true
1231
1305
  :virtualenv
1232
1306
  else
1307
+ status = !banner
1233
1308
  :venv
1234
1309
  end
1235
1310
  cmd, opts = python_session('-m', flag, opts: opts)
@@ -1237,34 +1312,31 @@ module Squared
1237
1312
  op.append(dir, delim: true)
1238
1313
  .clear(pass: false)
1239
1314
  status ||= op.arg?(/\A-v+\z/)
1240
- ret = run(op, env, exception: true, banner: banner)
1241
- args = []
1242
- requires = read_pyproject 'build-system', 'requires'
1243
- args.concat(requires) if requires.is_a?(Array)
1244
- if poetry?
1245
- requires = read_pyproject 'tool.poetry', 'requires-poetry'
1246
- args << "poetry#{requires}"
1247
- elsif setuptools? && !virtualenv?
1248
- args << 'setuptools' << 'wheel'
1249
- end
1250
- pip(:install, *args, banner: false) unless args.empty?
1251
- success?(ret, banner, !status) do |out|
1252
- if out && dir.directory?
1253
- if poetry?
1254
- dir += (windows? ? 'Scripts/python.exe' : 'bin/python')
1255
- cmd = poetry_session 'env use'
1256
- elsif pdm?
1257
- cmd = pdm_session 'use -f'
1258
- else
1259
- puts "Success: #{dir}"
1260
- return
1261
- end
1262
- cmd << shell_quote(dir)
1263
- print_run cmd, silent?
1264
- run(banner: false)
1315
+ if run(op, env, exception: true, banner: banner) && dir.directory?
1316
+ args = []
1317
+ requires = read_pyproject 'build-system', 'requires'
1318
+ args.concat(requires) if requires.is_a?(Array)
1319
+ if poetry?
1320
+ requires = read_pyproject 'tool.poetry', 'requires-poetry'
1321
+ args << "poetry#{requires}"
1322
+ elsif setuptools? && !virtualenv?
1323
+ args << 'setuptools' << 'wheel'
1324
+ end
1325
+ pip(:install, *args, banner: false) unless args.empty?
1326
+ if poetry?
1327
+ dir += (windows? ? 'Scripts/python.exe' : 'bin/python')
1328
+ poetry_session 'env use'
1329
+ elsif pdm?
1330
+ pdm_session 'use -f'
1265
1331
  else
1266
- puts 'Failed'
1332
+ puts "Success: #{dir}" unless status
1333
+ return
1267
1334
  end
1335
+ cmd << shell_quote(dir)
1336
+ print_run cmd, silent?
1337
+ run(banner: false)
1338
+ elsif !status
1339
+ puts 'Failed'
1268
1340
  end
1269
1341
  end
1270
1342
 
@@ -227,7 +227,7 @@ module Squared
227
227
  serve_set kwargs[:serve]
228
228
  gemfile_set kwargs[:gemspec]
229
229
  self.autodetect = autodetect
230
- @bundle = { exec: exec.is_a?(String) ? basepath!(exec) : exec }
230
+ @bundle = { exec: exec }
231
231
  @steepfile = basepath! steep if steep
232
232
  rubocop = rootpath('.rubocop.yml', ascend: Dir.home) if rubocop == true
233
233
  @rubocopfile = basepath! rubocop if rubocop
@@ -1601,7 +1601,7 @@ module Squared
1601
1601
 
1602
1602
  def rdbg(*args, banner: verbose?, with: nil, pass: PASS_RUBY[:rdbg], **kwargs)
1603
1603
  opts = session_opts(with, args: args, kwargs: kwargs, pass: pass)
1604
- cmd = session 'rdbg'
1604
+ cmd = session(*bundle_args('rdbg'))
1605
1605
  args.each_with_index do |val, i|
1606
1606
  break if val == '--'
1607
1607
  next unless exist?(val, type: 'f')
@@ -1881,7 +1881,7 @@ module Squared
1881
1881
  end
1882
1882
 
1883
1883
  def ruby_session(*cmd, **kwargs)
1884
- session('ruby', *preopts, *cmd, **kwargs)
1884
+ session(*bundle_args('ruby'), *preopts, *cmd, **kwargs)
1885
1885
  end
1886
1886
 
1887
1887
  def gem_session(*cmd, **kwargs)
@@ -1899,11 +1899,10 @@ module Squared
1899
1899
  end
1900
1900
 
1901
1901
  def rbs_session(*cmd, opts: nil)
1902
- args = bundle_args 'rbs'
1903
- return session(*args, *cmd) unless opts
1902
+ return session(*bundle_args('rbs'), *cmd) unless opts
1904
1903
 
1905
1904
  op = OptionPartition.new(opts, OPT_RUBY[:rbs], project: self, strict: strict?)
1906
- [session(*args, *op.to_a, *cmd), op.extras]
1905
+ [session(*bundle_args('rbs'), *op.to_a, *cmd), op.extras]
1907
1906
  end
1908
1907
 
1909
1908
  def gem_output(*cmd, **kwargs)
@@ -1911,7 +1910,7 @@ module Squared
1911
1910
  end
1912
1911
 
1913
1912
  def ruby_output(*cmd, **kwargs)
1914
- session_output('ruby', *cmd, **kwargs)
1913
+ session_output(*bundle_args('ruby'), *cmd, **kwargs)
1915
1914
  end
1916
1915
 
1917
1916
  def bundle_output(*cmd, **kwargs)
@@ -1924,7 +1923,20 @@ module Squared
1924
1923
 
1925
1924
  def bundle_args(*args)
1926
1925
  if (val = @bundle[:exec])
1927
- args.unshift(quote_option('gemfile', val)) unless val == true
1926
+ app = args.first
1927
+ if val.is_a?(Hash)
1928
+ env = val[app.to_s] || val[app.to_sym]
1929
+ return args unless env
1930
+
1931
+ val = env.is_a?(String) ? basepath!(env) : true
1932
+ elsif app == 'ruby'
1933
+ return args
1934
+ end
1935
+ if val.is_a?(String)
1936
+ return args unless (val = basepath!(val))
1937
+
1938
+ args.unshift("--gemfile=#{shell_escape(val.relative_path_from(path), quote: true)}")
1939
+ end
1928
1940
  args.unshift('bundle', 'exec')
1929
1941
  end
1930
1942
  args
@@ -37,12 +37,12 @@ module Squared
37
37
  end
38
38
  end
39
39
 
40
- def insert(*args)
41
- replace Set.new(compact.insert(*args))
40
+ def insert(...)
41
+ replace Set.new(compact.insert(...))
42
42
  end
43
43
 
44
- def slice(*args)
45
- compact.slice(*args)
44
+ def slice(...)
45
+ compact.slice(...)
46
46
  end
47
47
 
48
48
  def slice!(*args)
@@ -168,16 +168,16 @@ module Squared
168
168
  to_a.join(@delim)
169
169
  end
170
170
 
171
- def to_enum(*args)
172
- to_a.to_enum(*args)
171
+ def to_enum(...)
172
+ to_a.to_enum(...)
173
173
  end
174
174
 
175
- def to_json(*args)
176
- to_a.to_json(*args)
175
+ def to_json(...)
176
+ to_a.to_json(...)
177
177
  end
178
178
 
179
- def to_yaml(*args)
180
- to_a.to_yaml(*args)
179
+ def to_yaml(...)
180
+ to_a.to_yaml(...)
181
181
  end
182
182
 
183
183
  alias add :<<
@@ -248,7 +248,7 @@ module Squared
248
248
  end
249
249
 
250
250
  def parse(list, opts = extras, no: nil, single: nil, args: false, multiple: nil, first: nil, underscore: nil,
251
- explicit: false, bool: %w[true false], auto: [], stdin: false, &blk)
251
+ explicit: false, bool: %w[true false], auto: [], regexp: {}, enum: {}, stdin: false, &blk)
252
252
  @extras = []
253
253
  @values = []
254
254
  bare = []
@@ -265,8 +265,6 @@ module Squared
265
265
  bl = []
266
266
  ex = []
267
267
  ml = []
268
- re = {}
269
- ar = {}
270
268
  sw = {}
271
269
  se = {}
272
270
  list.flat_map do |val|
@@ -288,10 +286,10 @@ module Squared
288
286
  end
289
287
  if (n = val.index('='))
290
288
  flag = val[0, n]
291
- enum = flag.split(':')
292
- mod = if enum.size > 1
293
- pre = enum.shift
294
- enum.map { |s| "#{pre}:#{s}" }
289
+ opt = flag.split(':')
290
+ mod = if opt.size > 1
291
+ pre = opt.shift
292
+ opt.map { |s| "#{pre}:#{s}" }
295
293
  else
296
294
  [flag]
297
295
  end
@@ -326,14 +324,14 @@ module Squared
326
324
  pat = eval(val)
327
325
  raise SyntaxError unless pat.is_a?(Regexp)
328
326
 
329
- mod.each { |key| re[key] = pat }
327
+ mod.each { |key| regexp[key] = pat }
330
328
  rescue SyntaxError
331
329
  warn "#{self.class}: invalid pattern (#{val})"
332
330
  end
333
331
  when '{'
334
332
  group = val[(n + 2)..].chomp!('}')&.split(',')
335
333
  if group
336
- mod.each { |key| ar[key] = group }
334
+ mod.each { |key| enum[key] = group }
337
335
  else
338
336
  warn "#{self.class}: invalid group (#{val})"
339
337
  end
@@ -440,8 +438,8 @@ module Squared
440
438
  else
441
439
  push opt
442
440
  end
443
- elsif !ar.empty? && has.call(ar.keys, key)
444
- group = get.call(ar)
441
+ elsif !enum.empty? && has.call(enum.keys, key)
442
+ group = get.call(enum)
445
443
  if group.include?(val)
446
444
  add shell_option(key, val, force: false, **kwargs)
447
445
  elsif @strict
@@ -449,10 +447,10 @@ module Squared
449
447
  else
450
448
  push opt
451
449
  end
452
- elsif !re.empty? && has.call(re.keys, key)
453
- pat = get.call(re)
450
+ elsif !regexp.empty? && has.call(regexp.keys, key)
451
+ pat = get.call(regexp)
454
452
  if val.match?(pat)
455
- add quote_option(key, val, **kwargs)
453
+ add __send__(kwargs[:merge] ? :basic_option : :quote_option, key, val, **kwargs)
456
454
  elsif @strict
457
455
  clear([opt], hint: "match != /#{pat.source}/")
458
456
  else
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: squared
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0.pre2
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - An Pham
@@ -142,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
142
  - !ruby/object:Gem::Version
143
143
  version: '0'
144
144
  requirements: []
145
- rubygems_version: 4.0.20
145
+ rubygems_version: 4.0.23
146
146
  specification_version: 4
147
147
  summary: Rake task generator for managing multi-language workspaces.
148
148
  test_files: []