squared 0.9.0 → 0.9.2

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: 41f1d82942dcd7279ec182d1fc0bdc8f90ccc2e476860c6ca70bc89286417598
4
- data.tar.gz: e6b7773f70fd0d2867fe13bdae57324dd6c90048c8bea3b27937738edf4df78c
3
+ metadata.gz: 7bc8d196a7c5068373757f7bbb9ba037cafd30500875d924628690f57451b927
4
+ data.tar.gz: '00078ddc57f3f357cc9833a679c42d636936d0db91e75a327a07ba19dedd0165'
5
5
  SHA512:
6
- metadata.gz: cc02b93ac5fbfd34e23ff87cda66a3665fca3490c942e7a5942edb9ba6540eb6b95e1c87d8273444ec4bbac6d56841b7b3213295ce9ee856c15383794ecac97a
7
- data.tar.gz: ee1c5b726339754ee878ff97cd9994983cbc5ca4822d2ffa25170c2f3512414966f367dcc5c4f0f2bc1eb8156667b88fcffc294ccbab1468c57ef465bb387243
6
+ metadata.gz: c5b85d626962791e9da8355f82999b0ab8bcd1e63f88605c6a3dfecb8b5b515469373acb8f7d79eab4887b3b3f826e1d540133361b74e5c3e3160545215b3641
7
+ data.tar.gz: ee09108c1abea47c45ae2a931e01a9b1e42097dd597537796543321aaca03b9825d9942a26274c7fbca344f748b1d4b8765102dcd7b74f54489e7dc2115b5a97
data/CHANGELOG.md CHANGED
@@ -1,5 +1,81 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.2] - 2026-11-04
4
+
5
+ ### Added
6
+
7
+ - sqd: Option --no-gemfile for undoing bundle exec project wrapper.
8
+ - sqd: Option --commit for pushing files to remote branch.
9
+ - sqd: Option --mulitline for writing messages with newlines.
10
+ - sqd: Option --amend[-orig] for replacing current commit.
11
+ - sqd: Option --project can match common subtasks.
12
+
13
+ ### Changed
14
+
15
+ - Logger warn statements from project misconfiguration were downgraded.
16
+ - sqd: Task creation from joins are validated before consuming arguments.
17
+
18
+ ### Fixed
19
+
20
+ - sqd: Option --project did not support multiple patterns.
21
+
22
+ ## [0.8.8]
23
+
24
+ - See `0.7.15`.
25
+
26
+ ## [0.7.15]
27
+
28
+ ### Added
29
+
30
+ - Git command commit action pathspec was implemented.
31
+ - Common base argument TAB for column alignment was created.
32
+ - Node command package action deny can modify currently approved scripts.
33
+
34
+ ### Changed
35
+
36
+ - Node command package action [approve|deny] uses install-scripts ls.
37
+
38
+ ### Fixed
39
+
40
+ - Bundle exec wrapper as a Hash did not support using alternate Gemfile.
41
+ - Common base ARG data objects were not frozen with __freeze__.
42
+
43
+ ## [0.9.1] - 2026-10-22
44
+
45
+ ### Added
46
+
47
+ - sqd: Option --gemfile for rerouting commands through bundle exec.
48
+ - Python install manager for Windows is supported.
49
+ - sqd: Option --sync for running threads in a series was implemented.
50
+
51
+ ## [0.8.7]
52
+
53
+ ### Added
54
+
55
+ - OptionPartition can be initialized with enum and pattern options.
56
+
57
+ ### Fixed
58
+
59
+ - Python venv creation did not run post-install for package managers.
60
+
61
+ ## [0.7.14]
62
+
63
+ ### Changed
64
+
65
+ - Python program meson prefix options use local path resolution.
66
+ - Bundle exec wrapper can be configured per application.
67
+
68
+ ### Fixed
69
+
70
+ - Bundle exec wrapper did not accept boolean values.
71
+
72
+ ## [0.6.21]
73
+
74
+ ### Fixed
75
+
76
+ - RuboCop did not resolve config to project directory.
77
+ - Python program did not use correct separator for double dash options.
78
+
3
79
  ## [0.9.0] - 2026-10-12
4
80
 
5
81
  ### Added
@@ -63,7 +139,7 @@
63
139
 
64
140
  - OptionPartition did not parse quoted option values.
65
141
 
66
- ## [0.6.20] - 2026-09-25
142
+ ## [0.6.20]
67
143
 
68
144
  ### Added
69
145
 
@@ -2198,7 +2274,11 @@
2198
2274
 
2199
2275
  - Changelog was created.
2200
2276
 
2277
+ [0.9.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.9.2
2278
+ [0.9.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.9.1
2201
2279
  [0.9.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.9.0
2280
+ [0.8.8]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.8
2281
+ [0.8.7]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.7
2202
2282
  [0.8.6]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.6
2203
2283
  [0.8.5]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.5
2204
2284
  [0.8.4]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.4
@@ -2206,6 +2286,8 @@
2206
2286
  [0.8.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.2
2207
2287
  [0.8.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.1
2208
2288
  [0.8.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.8.0
2289
+ [0.7.15]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.15
2290
+ [0.7.14]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.14
2209
2291
  [0.7.13]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.13
2210
2292
  [0.7.12]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.12
2211
2293
  [0.7.11]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.11
@@ -2220,6 +2302,7 @@
2220
2302
  [0.7.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.2
2221
2303
  [0.7.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.1
2222
2304
  [0.7.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.7.0
2305
+ [0.6.21]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.21
2223
2306
  [0.6.20]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.20
2224
2307
  [0.6.19]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.19
2225
2308
  [0.6.18]: https://github.com/anpham6/squared-ruby/releases/tag/v0.6.18
data/README.md CHANGED
@@ -32,7 +32,7 @@ gem install squared
32
32
  There is an additional program wrapper for ``rake`` to be used when running batch commands and to enable global settings more easily.
33
33
 
34
34
  ```xml
35
- Usage: sqd [options] [rake options] [global...] [:targets...] -- [rake targets...]
35
+ Usage: sqd [options] [rake options] [pathspec...] -- [global...] [:targets...] -- [rake targets...]
36
36
 
37
37
  -R, --ref STR,STR Run global commands by class reference
38
38
  -G, --group STR,STR Run global commands by project group
@@ -43,18 +43,25 @@ 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
- --parallel STR,PAT Run standard tasks with multi-task threads
46
+ -u, --parallel STR,PAT Run standard tasks with multi-task threads
47
+ -U, --sync STR,PAT Run parallel tasks in a series sequentially
47
48
  -F, --force [STR,STR] Enable force through ENV by application
48
49
  -y, --yes [STR,STR] Enable auto-approve through ENV by application
49
50
  -d, --dry-run Run commands without any modifying actions
50
51
 
51
- -S, --select PAGE[:CHOICE] Prompt menu selection pagination display (e.g. 15:75)
52
+ -S, --select PAGE[:CHOICE] Prompt menu selection pagination display (e.g. 20:60)
53
+ -M, --multiline STR Use advanced editor with custom message terminator (e.g. EOF)
52
54
 
55
+ -g, --[no-]gemfile Execute commands using bundle exec and nearest Gemfile
53
56
  -p, --pipe 0|1|2 Set file descriptor for communication
54
57
  -o, --pipe-out FILE Write command data stream to a file
55
58
  --fail 0..4 Set application exception level
56
59
  --log-level SEVERITY Set logger and exception state (debug, info, warn, error, fatal)
57
60
 
61
+ -c, --commit Push files remotely on active branch from current directory
62
+ --amend Replace the tip of the active branch remotely with a force push
63
+ --amend-orig Perform an amend commit without changing the message
64
+
58
65
  --[no-]banner Display executing command attributes
59
66
  -x, --[no-]color Use enhanced styling for terminal output
60
67
  -X, --plain Disable banner and styling for all commands
@@ -102,11 +109,11 @@ Workspace::Application
102
109
  .clean("rake clean", group: "default")
103
110
  .clean(["build/"], group: "app")
104
111
  .log({ file: "tmp/%Y-%m-%d.log", level: "debug" }, group: "app")
105
- .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", group: "default", env: { # Ruby (with C extensions)
112
+ .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", exec: true, group: "default", env: { # Ruby (with C extensions)
106
113
  "CFLAGS" => "-fPIC -O1"
107
114
  })
108
- .add("optparse", doc: "rake rdoc", gemspec: "optparse.gemspec", group: "default") # Uses bundler/gem_tasks (without C extensions)
109
- .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"
115
+ .add("optparse", doc: "rake rdoc", gemspec: "optparse.gemspec", exec: { rake: true, ruby: '../Gemfile', rbs: false }, group: "default") # Uses bundler/gem_tasks (without C extensions)
116
+ .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
117
  self.gemdir = "/home/user/.local/gem/ruby/4.0.0/gems/logger-1.7.0" # Default for "into"
111
118
  end
112
119
  .add("e-mc", "emc", copy: { from: "publish", scope: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node)
data/exe/sqd CHANGED
@@ -6,36 +6,41 @@ require 'rake'
6
6
  require 'optparse'
7
7
 
8
8
  class SqdParser < OptionParser
9
- include Squared::Common::Format
9
+ include Squared
10
+ include Common::Format
10
11
  include Utils
11
12
 
13
+ OPT_DASH = /^--?[^-]/.freeze
14
+
12
15
  attr_reader :options, :global
16
+ attr_accessor :argv
13
17
 
14
18
  def initialize(*)
15
- super
16
19
  @options = { 'project-args': [], 'project-env': [] }
17
20
  @global = []
21
+ @argv = ARGV.dup
22
+ super
18
23
  @program_name = 'squared'
19
24
  @raise_unknown = false
20
25
  @require_exact = true
21
26
  end
22
27
 
23
28
  def parse_global(list, assert:)
24
- raise ArgumentError, "cannot be used with --#{assert}" if options.key?(assert)
25
-
26
- ARGV.each_with_index do |val, index|
29
+ error_exit "cannot be used with --#{assert}" if options.key?(assert)
30
+ each_with_index do |val, index|
27
31
  if val == '--'
28
32
  separator!(index: index)
29
33
  break
30
34
  end
31
- next if val.start_with?('-') || val.include?(':') || val.empty?
35
+ found = list.map { |name| "#{val}:#{name}" }.select { |name| tasks.include?(name) }
36
+ next if found.empty?
32
37
 
33
- global.concat(list.map { |name| "#{val}:#{name}" })
38
+ global.concat(found)
34
39
  ARGV[index] = ''
35
40
  end
36
41
  end
37
42
 
38
- def parse_common(list, session:, arg:)
43
+ def parse_common(list, arg:)
39
44
  if list && !list.include?('*')
40
45
  session[arg].merge!(list.to_h { |key| [key, 'true'] })
41
46
  else
@@ -43,18 +48,159 @@ class SqdParser < OptionParser
43
48
  end
44
49
  end
45
50
 
46
- def banner_set(val, session:)
51
+ def parse_series(list, flag:)
52
+ pattern, parallel = list.partition do |val|
53
+ next false if val.match?(%r{^[\w~`!@#%&\-=:;"'<>/]+$})
54
+
55
+ Regexp.new(val)
56
+ rescue RegexpError
57
+ false
58
+ end
59
+ s = flag == :sync ? '!' : ''
60
+ (session[:APP][:"build_pattern#{s}"] ||= []).concat(pattern.map { |pat| Regexp.new(pat) }) unless pattern.empty?
61
+ (session[:APP][:"build_parallel#{s}"] ||= []).concat(parallel) unless parallel.empty?
62
+ end
63
+
64
+ def parse_range(val, max:, arg:, min: 0)
65
+ error_exit('out of range', hint: val) unless val.between?(min, max)
66
+ session[arg] = val
67
+ end
68
+
69
+ def parse_rakefile
70
+ OptionParser.new do |opts|
71
+ opts.raise_unknown = false
72
+ opts.on('--rakefile', '-f [FILENAME]', '') do |val|
73
+ return val || ''
74
+ end
75
+ end.parse(ARGV.dup)
76
+ nil
77
+ end
78
+
79
+ def option_select(val)
80
+ page, choice = val.split(/\D/, 2).map(&:to_i)
81
+ error_exit('not an integer', hint: val) if choice.nil? ? page == 0 : choice == 0
82
+ session[:PAGE] = page if page > 0
83
+ session[:CHOICE] = choice if choice.to_i > 0
84
+ ENV['SELECT'] = "#{page}:#{choice}"
85
+ end
86
+
87
+ def option_multiline(val)
88
+ Common::Prompt.multiline = val
89
+ rescue NotImplementedError => e
90
+ warn log_warn(e, hint: argv.find { |val| multilineopts.include?(val) })
91
+ end
92
+
93
+ def option_gemfile(val)
94
+ require 'pathname'
95
+ session[:APP][:ruby_gemfile] = val
96
+ return unless val
97
+ return if ENV['RUBYOPT']&.match?(/(^|\b)-r.+#{File::SEPARATOR}bundler#{File::SEPARATOR}setup\b/)
98
+
99
+ gemfile = Pathname.pwd.ascend { |path| break path if path.join('Gemfile').file? } || ENV['BUNDLE_GEMFILE']
100
+ return unless gemfile && (argv.delete('--gemfile') || argv.delete('-g'))
101
+
102
+ argv.unshift('sqd')
103
+ if gemfile.is_a?(String)
104
+ gemfile = Common::Shell.shell_escape(Pathname.new(gemfile).relative_path_from(Dir.pwd), quote: true)
105
+ argv.unshift("--gemfile=#{gemfile}")
106
+ gemfile = Dir.pwd
107
+ end
108
+ ENV['BUNDLE_GEMFILE'] = nil
109
+ exec(Common::Shell.shell_bin('bundle'), 'exec', *argv, chdir: gemfile)
110
+ end
111
+
112
+ def option_loglevel(val)
113
+ require 'logger'
114
+ severity = case val
115
+ when :debug then Logger::DEBUG
116
+ when :info then Logger::INFO
117
+ when :warn then Logger::WARN
118
+ when :error then Logger::ERROR
119
+ when :fatal then Logger::FATAL
120
+ else
121
+ val.to_i
122
+ end
123
+ session[:LEVEL] = severity
124
+ ENV['LOG_LEVEL'] = severity.to_s
125
+ end
126
+
127
+ def option_commit(*opts, flag:)
128
+ require 'squared/workspace'
129
+ c = argv.index { |val| opts.include?(val) } || error_exit('option was not found', hint: opts.last)
130
+ check_exclusive('-c', '--commit', '--amend', '--amend-orig', flag: argv[c])
131
+ first = lambda do
132
+ if ARGV.empty?
133
+ m = argv.index { |val| multilineopts.include?(val) }
134
+ t = argv.index { |val| !val.match?(OPT_DASH) } || -1
135
+ return true if c == 0 || (m == 0 && c == 2) || t > c
136
+ end
137
+ !rakefile?
138
+ end
139
+ refs = []
140
+ found = argv[c.succ]
141
+ j = -1
142
+ each_with_index do |pat, i|
143
+ found = nil if pat == found
144
+ if i == j
145
+ ARGV[i.pred] = ''
146
+ ARGV[i] = ''
147
+ option_multiline pat
148
+ next
149
+ end
150
+ next if found
151
+
152
+ case pat
153
+ when '--'
154
+ ARGV[i] = ''
155
+ break
156
+ when *multilineopts
157
+ j = i.succ
158
+ next
159
+ when OPT_DASH
160
+ next if refs.empty? || Dir[pat].empty?
161
+ else
162
+ next if refs.empty? && Dir[pat].empty?
163
+ end
164
+ refs << pat
165
+ ARGV[i] = ''
166
+ end
167
+ if refs.empty?
168
+ if !`git status -s --ignore-submodules=all --ignored=no`.empty?
169
+ refs << '*'
170
+ elsif first.call
171
+ warn 'No files to commit.'
172
+ exit 1
173
+ else
174
+ return
175
+ end
176
+ end
177
+ Workspace::Application
178
+ .new
179
+ .add(File.basename(Dir.pwd), ref: :git) { commit(flag, refs: refs.uniq, pass: nil) }
180
+ exit if first.call
181
+ self.argv = ARGV.dup
182
+ end
183
+
184
+ def option_verbose
185
+ unless session[:VERBOSE]
186
+ session[:VERBOSE] = $VERBOSE ? 1 : 0
187
+ global.unshift('--verbose')
188
+ end
189
+ ENV['VERBOSE'] = (session[:VERBOSE] += 1).to_s
190
+ end
191
+
192
+ def banner_set(val)
47
193
  session[:BANNER] = val
48
194
  ENV['BANNER'] = val ? 'true' : 'false'
49
195
  end
50
196
 
51
- def color_set(val, session:)
197
+ def color_set(val)
52
198
  if (session[:COLOR] = val)
53
199
  enable_aixterm
54
200
  enable_drawing
55
201
  else
56
202
  enable_drawing false
57
- Squared::Common.const_get(:VAR)[:colors].freeze
203
+ Common.const_get(:VAR)[:colors].freeze
58
204
  end
59
205
  ENV['NO_COLOR'] = val ? nil : '1'
60
206
  ENV['FORCE_COLOR'] = val ? '2' : '0'
@@ -71,68 +217,111 @@ class SqdParser < OptionParser
71
217
  rescue RegexpError
72
218
  false
73
219
  end
220
+ subtasks = []
74
221
  pattern.each do |val|
75
222
  val = if val == '*'
76
223
  '[^:]+'
77
224
  else
78
225
  val.sub(/^\*/, '.*')
79
226
  .sub(/(?<!\.)\*$/, '.*')
80
- .gsub(/\.([*+])/, '[^:]\1?')
227
+ .gsub(/(?<!\\)\.([*+])(?!\?)/, '[^:]\1?')
81
228
  end
82
- IO.popen("rake -T \"^#{val}:\"").each { |line| project << $1 if line =~ /\Arake (#{val}):/o }
229
+ pat = /^(#{val.chomp(':')}):/
230
+ tasks.each do |task|
231
+ next unless task =~ pat
232
+
233
+ subtasks << $1
234
+ name = task.split(':', 2).first
235
+ project << name unless project.include?(name)
236
+ end
83
237
  end
84
238
  exit 1 if project.empty? && !pattern.empty?
85
- project.uniq!
239
+ subtasks.uniq!
86
240
  options[:'project-env'].each do |val|
87
241
  n = val.index('=')
88
- key = val[...n]
89
- val = val[n.succ..].stripquote
242
+ key = val[0...n]
243
+ val = val[n.succ..-1].stripquote
90
244
  project.each { |name| ENV["#{key}_#{env_key(name.split(':').first)}"] = val }
91
245
  end
92
- ARGV.each_with_index do |val, index|
246
+ each_with_index do |val, index|
93
247
  if val == '--'
94
248
  separator!(index: index)
95
249
  break
96
250
  end
97
251
  next unless val.start_with?(':')
98
252
 
99
- args = options[:'project-args'].shift
100
- global.concat(project.map { |name| "#{name}#{val}#{"[#{args}]" if args}" })
253
+ found = subtasks.map { |name| "#{name}#{val}" }.select { |name| tasks.include?(name) }
254
+ if (args = options[:'project-args'].shift)
255
+ found.map! { |name| "#{name}[#{args}]" }
256
+ end
257
+ global.concat(found)
101
258
  ARGV[index] = ''
102
259
  end
103
260
  end
104
- if options[:parallel]
105
- pattern, parallel = options[:parallel].partition do |val|
106
- next false if val.match?(%r{^[\w~`!@#%&\-=:;"'<>/]+$})
261
+ Rake.application.run(global.concat(ARGV))
262
+ end
107
263
 
108
- Regexp.new(val)
109
- rescue RegexpError
110
- false
264
+ def tasks
265
+ @tasks ||= Rake.with_application do |rake|
266
+ if (rakefile = parse_rakefile)
267
+ rakefiles = rake.instance_variable_get(:@rakefiles)
268
+ rakefiles.clear
269
+ rakefiles << rakefile
111
270
  end
112
- session = Squared::Common::ARG
113
- session[:APP][:build_pattern] = pattern.map { |pat| Regexp.new(pat) } unless pattern.empty?
114
- session[:APP][:build_parallel] = parallel unless parallel.empty?
115
- end
116
- ARGV.reject!(&:empty?)
117
- Rake.application.run(global.concat(ARGV))
271
+ rake.raw_load_rakefile
272
+ end.tasks.map(&:name)
118
273
  end
119
274
 
120
275
  def separator!(index:)
121
276
  ARGV[index] = '' if ARGV.grep('--').size > 1
122
277
  end
123
278
 
124
- def error_exit(*val, hint)
279
+ def check_exclusive(*args, flag:)
280
+ args.delete(flag)
281
+ return unless (n = index(*args))
282
+
283
+ error_exit "cannot be used with #{argv[n]}"
284
+ end
285
+
286
+ def error_exit(*val, hint: nil)
125
287
  raise OptionParser::InvalidArgument, message(*val, hint: hint), caller(1)
126
288
  end
289
+
290
+ def session
291
+ Common::ARG
292
+ end
293
+
294
+ def multilineopts
295
+ %w[-M --multiline]
296
+ end
297
+
298
+ def each_with_index(&blk)
299
+ ARGV.each_with_index(&blk)
300
+ ARGV.reject!(&:empty?)
301
+ end
302
+
303
+ def index(*args)
304
+ argv.each_with_index do |val, i|
305
+ return i if args.any? { |s| val == s || (s.start_with?('--') && val.start_with?("#{s}=")) }
306
+ end
307
+ nil
308
+ end
309
+
310
+ def warn(*args)
311
+ $stderr.puts(*args)
312
+ end
313
+
314
+ def rakefile?
315
+ Rake.application.find_rakefile_location || parse_rakefile ? true : false
316
+ end
127
317
  end
128
318
 
129
319
  SqdParser.new do |sqd|
130
- session = Squared::Common::ARG
131
- sqd.banner = 'Usage: sqd [options] [rake options] [global...] [:targets...] -- [rake targets...]'
320
+ sqd.banner = 'Usage: sqd [options] [rake options] [pathspec...] -- [global...] [:targets...] -- [rake targets...]'
132
321
  sqd.separator ''
133
322
  sqd.on('-R', '--ref STR,STR', Array, 'Run global commands by class reference') do |val|
134
323
  sqd.parse_global(val, assert: :group)
135
- session[:APP][:series_ref_min] = 1
324
+ sqd.session[:APP][:series_ref_min] = 1
136
325
  end
137
326
  sqd.on('-G', '--group STR,STR', Array, 'Run global commands by project group') do |val|
138
327
  sqd.parse_global(val, assert: :ref)
@@ -143,82 +332,84 @@ SqdParser.new do |sqd|
143
332
  sqd.options[:'project-args'] << val
144
333
  end
145
334
  sqd.on('-e', '--project-env KEY=STR', 'Provide command ENV variables for :targets (multiple)') do |val|
146
- sqd.error_exit('missing value', "#{val}=") unless val.include?('=')
335
+ sqd.error_exit('missing value', hint: "#{val}=") unless val.include?('=')
147
336
  sqd.options[:'project-env'] << val
148
337
  end
149
338
  sqd.separator ''
150
339
  sqd.on('-s', '--[no-]strict', 'Do not run commands with invalid arguments') do |val|
151
- session[:STRICT] = val
340
+ sqd.session[:STRICT] = val
152
341
  ENV['STRICT'] = val ? 'true' : 'false'
153
342
  end
154
343
  sqd.on('--update', 'Use update strategy for applicable commands') do
155
344
  ENV['UPDATE'] = 'true'
156
345
  end
157
- sqd.on('--parallel STR,PAT', Array, 'Run standard tasks with multi-task threads')
346
+ sqd.on('-u', '--parallel STR,PAT', Array, 'Run standard tasks with multi-task threads') do |val|
347
+ sqd.parse_series(val, flag: :parallel)
348
+ end
349
+ sqd.on('-U', '--sync STR,PAT', Array, 'Run parallel tasks in a series sequentially') do |val|
350
+ sqd.parse_series(val, flag: :sync)
351
+ end
158
352
  sqd.on('-F', '--force [STR,STR]', Array, 'Enable force through ENV by application') do |val|
159
- sqd.parse_common(val, session: session, arg: :FORCE)
353
+ sqd.parse_common(val, arg: :FORCE)
160
354
  end
161
355
  sqd.on('-y', '--yes [STR,STR]', Array, 'Enable auto-approve through ENV by application') do |val|
162
- sqd.parse_common(val, session: session, arg: :YES)
356
+ sqd.parse_common(val, arg: :YES)
163
357
  end
164
358
  sqd.on('-d', '--dry-run', 'Run commands without any modifying actions') do
165
359
  ENV['DRY_RUN'] = 'true'
166
360
  end
167
361
  sqd.separator ''
168
- sqd.on('-S', '--select PAGE[:CHOICE]', 'Prompt menu selection pagination display (e.g. 15:75)') do |val|
169
- page, choice = val.split(/\D/, 2).map(&:to_i)
170
- sqd.error_exit('not an integer', val) if choice.nil? ? page == 0 : choice == 0
171
- session[:PAGE] = page if page > 0
172
- session[:CHOICE] = choice if choice.to_i > 0
173
- ENV['SELECT'] = "#{page}:#{choice}"
362
+ sqd.on('-S', '--select PAGE[:CHOICE]', 'Prompt menu selection pagination display (e.g. 20:60)') do |val|
363
+ sqd.option_select(val)
364
+ end
365
+ sqd.on('-M', '--multiline STR', 'Use advanced editor with custom message terminator (e.g. EOF)') do |val|
366
+ sqd.option_multiline(val)
174
367
  end
175
368
  sqd.separator ''
369
+ sqd.on('-g', '--[no-]gemfile', 'Execute commands using bundle exec and nearest Gemfile') do |val|
370
+ sqd.option_gemfile(val)
371
+ end
176
372
  sqd.on('-p', '--pipe 0|1|2', Integer, 'Set file descriptor for communication') do |val|
177
- sqd.error_exit('out of range', val) unless val.between?(0, 2)
178
- session[:PIPE] = val
373
+ sqd.parse_range(val, max: 2, arg: :PIPE)
179
374
  end
180
375
  sqd.on('-o', '--pipe-out FILE', 'Write command data stream to a file') do |val|
181
- sqd.error_exit("missing an explicit \".#{File::SEPARATOR}\"", val) unless val.include?(File::SEPARATOR)
182
- session[:OUT] = val
376
+ sqd.error_exit("missing an explicit \".#{File::SEPARATOR}\"", hint: val) unless val.include?(File::SEPARATOR)
377
+ sqd.session[:OUT] = val
183
378
  end
184
379
  sqd.on('--fail 0..4', Integer, 'Set application exception level') do |val|
185
- sqd.error_exit('out of range', val) unless val.between?(0, 4)
186
- session[:FAIL] = val
380
+ sqd.parse_range(val, max: 4, arg: :FAIL)
187
381
  end
188
382
  %i[debug info warn error fatal].then do |state|
189
383
  values = state + (0..4).map(&:to_s)
190
384
  sqd.on('--log-level SEVERITY', values, "Set logger and exception state (#{state.join(', ')})") do |val|
191
- require 'logger'
192
- severity = case val
193
- when :debug then Logger::DEBUG
194
- when :info then Logger::INFO
195
- when :warn then Logger::WARN
196
- when :error then Logger::ERROR
197
- when :fatal then Logger::FATAL
198
- else
199
- val.to_i
200
- end
201
- session[:LEVEL] = severity
202
- ENV['LOG_LEVEL'] = severity.to_s
385
+ sqd.option_loglevel(val)
386
+ end
387
+ end
388
+ if Pathname.pwd.join('.git').directory?
389
+ sqd.separator ''
390
+ sqd.on('-c', '--commit', 'Push files remotely on active branch from current directory') do
391
+ sqd.option_commit('-c', '--commit', flag: :pathspec)
392
+ end
393
+ sqd.on('--amend', 'Replace the tip of the active branch remotely with a force push') do
394
+ sqd.option_commit('--amend', flag: :amend)
395
+ end
396
+ sqd.on('--amend-orig', 'Perform an amend commit without changing the message') do
397
+ sqd.option_commit('--amend-orig', flag: :'amend-orig')
203
398
  end
204
399
  end
205
400
  sqd.separator ''
206
401
  sqd.on('--[no-]banner', 'Display executing command attributes') do |val|
207
- sqd.banner_set(val, session: session)
402
+ sqd.banner_set(val)
208
403
  end
209
404
  sqd.on('-x', '--[no-]color', 'Use enhanced styling for terminal output') do |val|
210
- sqd.color_set(val, session: session)
405
+ sqd.color_set(val)
211
406
  end
212
407
  sqd.on('-X', '--plain', 'Disable banner and styling for all commands') do
213
- sqd.banner_set(false, session: session)
214
- sqd.color_set(false, session: session)
408
+ sqd.banner_set(false)
409
+ sqd.color_set(false)
215
410
  end
216
411
  sqd.on('-v', '--verbose', 'Increase output verbosity (multiple)') do
217
- unless session[:VERBOSE]
218
- session[:VERBOSE] = $VERBOSE ? 1 : 0
219
- sqd.global.unshift('--verbose')
220
- end
221
- ENV['VERBOSE'] = (session[:VERBOSE] += 1).to_s
412
+ sqd.option_verbose
222
413
  end
223
414
  sqd.on('--version', 'Print the current version') do
224
415
  puts Squared::VERSION
@@ -17,6 +17,7 @@ module Squared
17
17
  BANNER: true,
18
18
  PAGE: 20,
19
19
  CHOICE: 100,
20
+ TAB: 4,
20
21
  QUOTE: "'",
21
22
  SPACE: ' => ',
22
23
  SUCCESS: 'Success',
@@ -105,6 +106,7 @@ module Squared
105
106
 
106
107
  def __freeze__
107
108
  PATH.freeze
109
+ ARG.each_value { |val| val.freeze if val.is_a?(::Hash) || val.is_a?(::Array) }
108
110
  ARG.freeze
109
111
  VAR.each_value(&:freeze)
110
112
  VAR[:theme].each_value { |val| val.freeze.each_value(&:freeze) }