squared 0.1.0 → 0.1.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: 5d5c87e8fd0e459e55e900839ba9f5f43e51ee1990d1fa103f9de264ae94c4e1
4
- data.tar.gz: 1e38b9df0a7d3f2b48d6912b880d1c2d593dcf04a1942a01791b84ea8b9e023c
3
+ metadata.gz: 5fb2101a0fb0c5a8f651bf471b44a5dc3be21c3b87d6e53f708d752b6e8c8ffd
4
+ data.tar.gz: eb322ca0cab89e5f41f719e06e182120efd96280f88a5f2625f3954331500a19
5
5
  SHA512:
6
- metadata.gz: f4b9df6f9d39a24bd1db9e056ab4b5864e36a6d5daf46403881845e3d1d725033b31a4c9d11274595a74c59dbf13ac77845e96a0c3e2f74c09317a3a1f123a62
7
- data.tar.gz: fcd0370d7ba7b6b590c9fec4a411a302dde15c8124f832aa5ed1a1e58cef209481ac9d595a5d8d385fefce87a8dc0e53a9208b0da6916c00d3a3e8f853748813
6
+ metadata.gz: a6947106acb6d33789f3ae79a84e87c3119b799f556a8570920904338b3955b795578f392860fce83260af5b1c17159ba4c64e9f4eeeaed6b5886e06dfe12a06
7
+ data.tar.gz: dd9416a0457a2ead233f32c9592b2061e569161d54482d4ab949fd7acb6ceb6139c26329a4fdb6ea1853ab6f840ab145e17f4b58a386bd68eb066c14ea3f85cd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.2] - 2024-12-23
4
+
5
+ ### Added
6
+
7
+ - Git command tag was implemented.
8
+
9
+ ### Deprecated
10
+
11
+ - Repo static method empty? was relocated into Application.
12
+
13
+ ### Fixed
14
+
15
+ - Namespace prefix was sometimes missing or duplicated.
16
+ - Silent operation used undefined variable.
17
+ - Unnecessary shell option escapes were removed.
18
+ - Ruby command outdated did not display in order when threaded.
19
+ - Regexp uses \A|\z format and not ^|$ for standard matches.
20
+ - Rake being called by a project did not escape options.
21
+ - Workspace did not check base project for Windows filename.
22
+ - Regexp for SemVer did not recognize package names.
23
+
24
+ ## [0.1.1] - 2024-12-14
25
+
26
+ ### Added
27
+
28
+ - Node package manager update command was implemented.
29
+ - Git pull and fetch retrieve options were expanded.
30
+
31
+ ### Changed
32
+
33
+ - Git show does not require an object argument.
34
+ - Project base class is implemented through Application.
35
+
36
+ ### Fixed
37
+
38
+ - Listing tasks did not check for rake -C option availability.
39
+ - Listing gems did not check for gem -C option availability.
40
+ - Disabled extension tasks by individual project were being created.
41
+ - Git commit did not locate origin from nested branches.
42
+ - Project clean command used incompatible folder delete option.
43
+ - Rake did not set original dir when calling itself.
44
+ - Rake did not set original rakefile when calling itself.
45
+ - Extended tasks were not associated to their supporting class method.
46
+
3
47
  ## [0.1.0] - 2024-12-7
4
48
 
5
49
  ### Added
@@ -27,5 +71,7 @@
27
71
 
28
72
  - Changelog was created.
29
73
 
74
+ [0.1.2]: https://github.com/anpham6/squared/releases/tag/v0.1.2-ruby
75
+ [0.1.1]: https://github.com/anpham6/squared/releases/tag/v0.1.1-ruby
30
76
  [0.1.0]: https://github.com/anpham6/squared/releases/tag/v0.1.0-ruby
31
77
  [0.0.12]: https://github.com/anpham6/squared/releases/tag/v0.0.12-ruby
data/README.md CHANGED
@@ -108,7 +108,7 @@ Workspace management uses [Ruby](https://www.ruby-lang.org/en/documentation/inst
108
108
  mkdir workspaces
109
109
  cd workspaces # REPO_ROOT
110
110
 
111
- wget https://raw.githubusercontent.com/anpham6/squared/master/Rakefile
111
+ wget https://unpkg.com/squared/Rakefile
112
112
 
113
113
  rake -T # List tasks
114
114
 
data/README.ruby.md CHANGED
@@ -37,17 +37,23 @@ Projects from any accessible folder can be added relative to the parent director
37
37
  require "squared"
38
38
 
39
39
  require "squared/workspace"
40
- require "squared/workspace/repo" # Optional
41
- require "squared/workspace/project/node" #
42
- require "squared/workspace/project/python" #
43
- require "squared/workspace/project/ruby" #
40
+ require "squared/workspace/repo" # Optional
41
+ require "squared/workspace/project/node" #
42
+ require "squared/workspace/project/python" #
43
+ require "squared/workspace/project/ruby" #
44
44
  # OR
45
- require "squared/app" # All workspace related modules
45
+ require "squared/app" # All workspace related modules
46
46
 
47
47
  # NODE_ENV = production
48
- # REPO_ROOT = /workspaces
49
- # REPO_HOME = /workspaces/squared
50
- # rake = /workspaces/squared/Rakefile
48
+
49
+ # REPO_ROOT = /workspaces #
50
+ # REPO_HOME = /workspaces/squared # Dir.pwd
51
+ # rake = /workspaces/squared/Rakefile # main?
52
+ # OR
53
+ # REPO_ROOT = /workspaces # Dir.pwd
54
+ # rake = /workspaces/Rakefile #
55
+ # REPO_HOME = /workspaces/squared # main: "squared"
56
+
51
57
  # pathname = /workspaces/pathname
52
58
  # optparse = /workspaces/optparse
53
59
  # log = /workspaces/logger
@@ -9,7 +9,7 @@ module Squared
9
9
  extend Forwardable
10
10
 
11
11
  def self.to_s
12
- super.match(/[^:]+$/)[0]
12
+ super.match(/[^:]+\z/)[0]
13
13
  end
14
14
 
15
15
  def_delegators :@data, :+, :each, :each_with_index, :entries, :to_a, :include?
@@ -31,7 +31,7 @@ module Squared
31
31
 
32
32
  class JoinSet < Set
33
33
  def self.to_s
34
- super.match(/[^:]+$/)[0]
34
+ super.match(/[^:]+\z/)[0]
35
35
  end
36
36
 
37
37
  def initialize(data = [], delim: ' ')
@@ -46,7 +46,7 @@ module Squared
46
46
  if pat && index != 0
47
47
  return val unless (data = pat.match(val))
48
48
 
49
- ret = index == -1 ? data.to_a[1..-1] : data[index]
49
+ ret = index == -1 ? data.to_a.drop(1) : data[index]
50
50
  else
51
51
  ret = val
52
52
  index = 0
@@ -170,7 +170,7 @@ module Squared
170
170
  args = args.map(&:to_s)
171
171
  if args.size > 1
172
172
  title = log_title(level, color: false)
173
- sub = { pat: /^(#{title})(.+)$/, styles: __get__(:theme)[:logger][log_sym(level)] } if color
173
+ sub = { pat: /\A(#{title})(.+)\z/m, styles: __get__(:theme)[:logger][log_sym(level)] } if color
174
174
  emphasize(args, title: title + (subject ? " #{subject}" : ''), sub: sub)
175
175
  else
176
176
  msg = [log_title(level, color: color)]
@@ -235,8 +235,8 @@ module Squared
235
235
  sub.each { |h| s = sub_style(s, **h) }
236
236
  s = "| #{s} |"
237
237
  if border
238
- s = sub_style(s, pat: /^(\|)(.+)$/m, styles: border)
239
- s = sub_style(s, pat: /^(.+)(\|)$/m, styles: border, index: 2)
238
+ s = sub_style(s, pat: /\A(\|)(.+)\z/m, styles: border)
239
+ s = sub_style(s, pat: /\A(.+)(\|)\z/m, styles: border, index: 2)
240
240
  end
241
241
  s
242
242
  end
@@ -8,8 +8,8 @@ module Squared
8
8
  def confirm(msg, default = nil, agree: 'Y', cancel: 'N', attempts: 5, timeout: 15)
9
9
  require 'readline'
10
10
  require 'timeout'
11
- agree = /^#{agree}$/i if agree.is_a?(::String)
12
- cancel = /^#{cancel}$/i if cancel.is_a?(::String)
11
+ agree = /\A#{agree}\z/i if agree.is_a?(::String)
12
+ cancel = /\A#{cancel}\z/i if cancel.is_a?(::String)
13
13
  Timeout.timeout(timeout) do
14
14
  begin
15
15
  while (ch = Readline.readline(msg, true))
@@ -8,38 +8,51 @@ module Squared
8
8
  module Shell
9
9
  module_function
10
10
 
11
- def shell_escape(val, quote: false)
12
- val = val.to_s
13
- return ::Shellwords.escape(val) unless ::Rake::Win32.windows?
11
+ def shell_escape(val, quote: false, force: false)
12
+ if (data = /\A(--?[^= ]+)((=|\s+)(["'])?(.+?)(["'])?)?\z/m.match(val = val.to_s))
13
+ return val unless data[2]
14
14
 
15
- quote ? shell_quote(val, force: false) : val
15
+ join = ->(opt) { data[1] + data[3] + shell_quote(opt) }
16
+ if data[4] == data[6]
17
+ data[4] ? val : join.(data[5])
18
+ else
19
+ join.("#{data[4]}#{data[5]}#{data[6]}")
20
+ end
21
+ elsif Rake::Win32.windows?
22
+ quote ? shell_quote(val, force: force) : val
23
+ else
24
+ Shellwords.escape(val)
25
+ end
16
26
  end
17
27
 
18
28
  def shell_quote(val, force: true)
19
- ret = val.to_s.strip
20
- return ret if (!force && !ret.include?(' ')) || ret =~ /(?:^|\S=|[^=]\s+)(["']).+\1$/m
29
+ val = val.to_s
30
+ return val if (!force && !val.include?(' ')) || val =~ /(?:^|\S=|[^=]\s+)(["']).+\1\z/m
21
31
 
22
- ::Rake::Win32.windows? ? "\"#{double_quote(ret)}\"" : "'#{single_quote(ret)}'"
32
+ Rake::Win32.windows? ? "\"#{double_quote(val)}\"" : "'#{single_quote(val)}'"
23
33
  end
24
34
 
25
- def shell_split(val, quote: false, join: false)
26
- ret = ::Shellwords.split(val).map do |opt|
27
- if (data = /^(--?[^= ]+)(=|\s+)?(["'])?(.+?)\3?$/m.match(opt))
28
- next opt unless data[2] && !data[3]
35
+ def shell_split(val, quote: false, join: nil)
36
+ val = Shellwords.split(val).map { |opt| shell_escape(opt, quote: quote) }
37
+ return val unless join
29
38
 
30
- data[1] + data[2] + shell_quote(data[4], force: !::Rake::Win32.windows?)
31
- else
32
- shell_escape(opt, quote: quote)
33
- end
34
- end
35
- join ? ret.join(' ') : ret
39
+ val.join(join.is_a?(::String) ? join : ' ')
40
+ end
41
+
42
+ def shell_option(flag, val = nil, quote: false, escape: true)
43
+ "--#{flag}#{if val
44
+ "=#{if escape
45
+ shell_escape(val, quote: quote)
46
+ else
47
+ quote ? shell_quote(val) : val
48
+ end}"
49
+ end}"
36
50
  end
37
51
 
38
52
  def fill_option(val)
39
- return "-#{val}" if val.size == 1 || val =~ /^[a-z]\d+$/i
53
+ return "-#{val}" if val.size == 1 || val =~ /\A[a-z]\d+\z/i
40
54
 
41
- val = "--#{val}" unless val.start_with?('-')
42
- shell_escape(val).sub('\\=', '=')
55
+ shell_escape(val.start_with?('-') ? val : "--#{val}")
43
56
  end
44
57
 
45
58
  def single_quote(val)
@@ -9,7 +9,7 @@ module Squared
9
9
  module_function
10
10
 
11
11
  def shell(*args, **kwargs)
12
- if RUBY_VERSION =~ /^2\.[0-5]\./
12
+ if RUBY_VERSION < '2.6'
13
13
  exception = kwargs.delete(:exception)
14
14
  ret = system(*args, **kwargs)
15
15
  return ret if ret || !exception
@@ -12,7 +12,7 @@ module Squared
12
12
  include Rake::DSL
13
13
 
14
14
  def self.to_s
15
- super.match(/[^:]+$/)[0]
15
+ super.match(/[^:]+\z/)[0]
16
16
  end
17
17
 
18
18
  attr_reader :main, :name, :project, :theme
@@ -199,17 +199,18 @@ module Squared
199
199
  title = Pathname.new(file)
200
200
  .realpath
201
201
  .to_s
202
- .sub(Regexp.new("^#{Regexp.escape(File.join(Dir.pwd, ''))}"), '')
202
+ .sub(Regexp.new("\\A#{Regexp.escape(File.join(Dir.pwd, ''))}"), '')
203
203
  emphasize(lines, title: title, sub: unless stdin?
204
204
  [
205
- { pat: /^((?:[^:]|(?<! ):(?! ))+)$/, styles: theme[:banner] },
206
- { pat: /^(.*?)(<[^>]+>)(.+)$/m, styles: theme[:undefined], index: 2 },
207
- { pat: /^(.+)( : (?!undefined).+)$/m, styles: theme[:key] },
208
- { pat: /^(.+ : )(-?[\d.]+)(\s*)$/m, styles: theme[:number], index: 2 },
209
- { pat: /^(.+ : ")(.+)("\s*)$/m, styles: theme[:string], index: 2 },
210
- { pat: /^(.+ : \{)(.+)(\}\s*)$/m, styles: theme[:hash], index: 2 },
211
- { pat: /^(.+ : \[)(.+)(\]\s*)$/m, styles: theme[:array], index: 2 },
212
- { pat: /^(.+ : (?!undefined))([^"\[{].*)$/m, styles: theme[:value],
205
+ { pat: /\A((?:[^:]|(?<! ):(?! ))+)\z/, styles: theme[:banner] },
206
+ { pat: /\A(.*?)(<[^>]+>)(.+)\z/m, styles: theme[:undefined], index: 2 },
207
+ { pat: /\A(.+)( : (?!undefined).+)\z/m, styles: theme[:key] },
208
+ { pat: /\A(.+ : )(-?[\d.]+)(\s*)\z/m, styles: theme[:number],
209
+ index: 2 },
210
+ { pat: /\A(.+ : ")(.+)("\s*)\z/m, styles: theme[:string], index: 2 },
211
+ { pat: /\A(.+ : \{)(.+)(\}\s*)\z/m, styles: theme[:hash], index: 2 },
212
+ { pat: /\A(.+ : \[)(.+)(\]\s*)\z/m, styles: theme[:array], index: 2 },
213
+ { pat: /\A(.+ : (?!undefined))([^"\[{].*)\z/m, styles: theme[:value],
213
214
  index: 2 }
214
215
  ]
215
216
  end)
@@ -261,6 +262,8 @@ module Squared
261
262
  end
262
263
 
263
264
  def format_desc(command, *ext, exist: exist?)
265
+ return unless Rake::TaskManager.record_task_metadata
266
+
264
267
  val = "#{ext.first}[#{exist ? '' : "file?=#{File.basename(main)}.#{ext.last},"}keys+]"
265
268
  message(@prefix, *name.split(':'), command, val, empty: true)
266
269
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.2'
5
5
  end
@@ -18,12 +18,19 @@ module Squared
18
18
  private_constant :SCRIPT_OBJ
19
19
 
20
20
  class << self
21
- def implement(*objs)
21
+ def implement(*objs, base: false)
22
+ return if base && objs.size > 1
23
+
22
24
  objs.each do |obj|
23
- next unless obj < impl_project
25
+ next unless base || obj < impl_project
24
26
 
25
- kind_project.unshift(obj)
26
- obj.tasks&.each { |task| impl_series.add(task, obj) }
27
+ if base
28
+ @impl_project = obj
29
+ impl_series.base_set(obj)
30
+ else
31
+ kind_project.unshift(obj)
32
+ obj.tasks&.each { |task| impl_series.add(task, obj) }
33
+ end
27
34
  if (args = obj.batchargs)
28
35
  impl_series.batch(*args)
29
36
  end
@@ -49,7 +56,7 @@ module Squared
49
56
  end
50
57
 
51
58
  def to_s
52
- super.match(/[^:]+$/)[0]
59
+ super.match(/[^:]+\z/)[0]
53
60
  end
54
61
 
55
62
  attr_reader :kind_project
@@ -63,8 +70,14 @@ module Squared
63
70
  def initialize(home = Dir.pwd, *, main: nil, prefix: nil,
64
71
  verbose: ARG[:VERBOSE], common: ARG[:COMMON], pipe: ARG[:PIPE], exception: ARG[:FAIL], **)
65
72
  @home = Pathname.new(home).realdirpath
73
+ basename = @home.basename.to_s
74
+ if main
75
+ @main = main.to_s.freeze
76
+ @home = @home.join(@main) unless @main == basename || (windows? && @main.downcase == basename.downcase)
77
+ else
78
+ @main = basename.freeze
79
+ end
66
80
  @root = @home.parent
67
- @main = (main || @home.basename).to_s.freeze
68
81
  @prefix = prefix
69
82
  @series = Application.impl_series.new(self)
70
83
  @project = {}
@@ -315,7 +328,7 @@ module Squared
315
328
 
316
329
  def task_name(val, desc: false)
317
330
  ret = @prefix ? task_join(@prefix, val) : val.to_s
318
- desc ? ret.split(':').join(ARG[:SPACE]) : ret
331
+ desc ? message(*ret.split(':')) : ret
319
332
  end
320
333
 
321
334
  def task_namespace(val, first: false)
@@ -376,7 +389,7 @@ module Squared
376
389
 
377
390
  return ret
378
391
  end
379
- @script[:ref!][:_] || SCRIPT_OBJ
392
+ @script[:ref!][:''] || SCRIPT_OBJ
380
393
  end
381
394
 
382
395
  def script_get(*args, group: nil, ref: nil)
@@ -402,7 +415,7 @@ module Squared
402
415
  return ret if group && (ret = @banner[:group][group.to_sym])
403
416
 
404
417
  ref.reverse_each { |val| return ret if (ret = @banner[:ref][val]) }
405
- @banner[:ref][:_]
418
+ @banner[:ref][:'']
406
419
  end
407
420
 
408
421
  def enabled?
@@ -437,6 +450,10 @@ module Squared
437
450
  !(proj = find(home)).nil? && proj.enabled?
438
451
  end
439
452
 
453
+ def windows?
454
+ Rake::Win32.windows?
455
+ end
456
+
440
457
  def rootpath(*args)
441
458
  root.join(*args)
442
459
  end
@@ -498,7 +515,21 @@ module Squared
498
515
  elsif ref
499
516
  as_a(ref).each { |val| @script[:ref!][val.to_sym] = data }
500
517
  else
501
- @script[:ref!][:_] = data
518
+ @script[:ref!][:''] = data
519
+ end
520
+ end
521
+
522
+ def root?(path, pass: [])
523
+ return false unless path.directory?
524
+
525
+ case path.children.size
526
+ when 0
527
+ true
528
+ when 1
529
+ target = path.children.first
530
+ target.to_s == __FILE__ || pass.any? { |val| val == target.basename.to_s }
531
+ else
532
+ false
502
533
  end
503
534
  end
504
535
 
@@ -14,8 +14,8 @@ module Squared
14
14
  include Utils
15
15
  include Rake::DSL
16
16
 
17
- VAR_SET = %i[parent global envname dependfile theme run script env depend graph doc test copy clean].freeze
18
- SEM_VER = /(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?/.freeze
17
+ VAR_SET = %i[parent global envname dependfile theme run script env].freeze
18
+ SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?\b/.freeze
19
19
  private_constant :VAR_SET, :SEM_VER
20
20
 
21
21
  class << self
@@ -25,7 +25,7 @@ module Squared
25
25
  def bannerargs(*); end
26
26
 
27
27
  def tasks
28
- [].freeze
28
+ %i[build depend graph doc test copy clean].freeze
29
29
  end
30
30
 
31
31
  def as_path(val)
@@ -46,7 +46,7 @@ module Squared
46
46
  end
47
47
 
48
48
  def to_s
49
- super.match(/[^:]+$/)[0]
49
+ super.match(/[^:]+\z/)[0]
50
50
  end
51
51
  end
52
52
 
@@ -264,9 +264,9 @@ module Squared
264
264
  val
265
265
  end
266
266
  emphasize(out, title: path, right: true, border: borderstyle, sub: [
267
- { pat: /^(#{Regexp.escape(path.to_s)})(.*)$/, styles: theme[:header] },
268
- { pat: /^(#{Regexp.escape(name)})(.*)$/, styles: theme[:active] },
269
- { pat: /^(.+ )(\()(\d+)(\))(.*)$/, styles: theme[:inline], index: 3 }
267
+ { pat: /\A(#{Regexp.escape(path.to_s)})(.*)\z/, styles: theme[:header] },
268
+ { pat: /\A(#{Regexp.escape(name)})(.*)\z/, styles: theme[:active] },
269
+ { pat: /\A(.+ )(\()(\d+)(\))(.*)\z/, styles: theme[:inline], index: 3 }
270
270
  ])
271
271
  end
272
272
  end
@@ -288,10 +288,10 @@ module Squared
288
288
 
289
289
  def add(path, name = nil, **kwargs, &blk)
290
290
  checkdir = lambda do |val|
291
- if val.directory? && val.exist?
291
+ if val.directory? && !val.empty?
292
292
  true
293
293
  else
294
- log.warn "workspace \"#{val}\" (not found)"
294
+ log.warn "workspace \"#{val}\" (#{val.empty? ? 'empty' : 'not found'})"
295
295
  false
296
296
  end
297
297
  end
@@ -383,7 +383,7 @@ module Squared
383
383
  next unless dir.directory?
384
384
 
385
385
  log.warn "rm -rf #{dir}"
386
- dir.rmtree(verbose: true)
386
+ dir.rmtree
387
387
  else
388
388
  files = val.include?('*') ? Dir[basepath(val)] : [basepath(val)]
389
389
  files.each do |file|
@@ -419,7 +419,7 @@ module Squared
419
419
  def variable_set(key, *val, **kwargs)
420
420
  if variables.include?(key)
421
421
  case key
422
- when :run
422
+ when :build, :run
423
423
  run_set(*val, **kwargs)
424
424
  when :script
425
425
  script_set(*val, **kwargs)
@@ -546,18 +546,17 @@ module Squared
546
546
  puts_oe(*args, pipe: pipe)
547
547
  end
548
548
 
549
- def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, **)
549
+ def run(cmd = @session, var = nil, exception: @exception, sync: true, banner: true, chdir: path, **)
550
550
  cmd = session_done(cmd)
551
551
  log.info cmd
552
552
  begin
553
- if cmd =~ /^\S+:(\S+:?)+$/ && workspace.task_defined?(cmd)
554
- print_item if sync
553
+ if cmd =~ /\A[^:]+:[^:]/ && workspace.task_defined?(cmd)
555
554
  log.warn "ENV was discarded: #{var}" if var
556
555
  task_invoke(cmd, exception: exception, warning: warning?)
557
556
  else
558
557
  print_item format_banner(cmd, banner: banner) if sync
559
558
  args = var.is_a?(Hash) ? [var, cmd] : [cmd]
560
- shell(*args, chdir: path, exception: exception)
559
+ shell(*args, chdir: chdir, exception: exception)
561
560
  end
562
561
  rescue StandardError => e
563
562
  log.error e
@@ -750,6 +749,8 @@ module Squared
750
749
  end
751
750
 
752
751
  def format_desc(action, flag, opts = nil, req: nil, arg: 'opts*')
752
+ return unless Rake::TaskManager.record_task_metadata
753
+
753
754
  opts = "#{arg}=#{opts.join(',')}" if opts.is_a?(Array)
754
755
  out = [@desc]
755
756
  if flag
@@ -772,7 +773,7 @@ module Squared
772
773
  end
773
774
  if verbose
774
775
  out = []
775
- out << cmd.sub(/^\S+/, &:upcase) if data[:command]
776
+ out << cmd.sub(/\A\S+/, &:upcase) if data[:command]
776
777
  data[:order].each do |val|
777
778
  if val.is_a?(Array)
778
779
  s = ' '
@@ -794,7 +795,7 @@ module Squared
794
795
  out << val.to_s
795
796
  end
796
797
  print_banner(*out, styles: data[:styles], border: data[:border], client: client)
797
- elsif multiple && workspace.series.multiple?
798
+ elsif workspace.series.multiple?
798
799
  "## #{__send__(data[:order].first || :path)} ##"
799
800
  end
800
801
  end
@@ -819,10 +820,10 @@ module Squared
819
820
  end
820
821
  if from
821
822
  out << from
822
- pat = /^(#{Regexp.escape(out.last)})(.*)$/
823
+ pat = /\A(#{Regexp.escape(out.last)})(.*)\z/m
823
824
  end
824
825
  else
825
- pat = /^(\s*\d+\.)(.+)$/
826
+ pat = /\A(\s*\d+\.)(.+)\z/m
826
827
  end
827
828
  sub = [headerstyle]
828
829
  sub << { pat: pat, styles: theme[:active] } if pat
@@ -834,15 +835,32 @@ module Squared
834
835
  end
835
836
 
836
837
  def append_repeat(flag, opts)
837
- opts.each { |val| @session << "--#{flag}=#{shell_escape(val, quote: true)}" }
838
+ opts.each { |val| @session << shell_option(flag, val, quote: true) }
839
+ end
840
+
841
+ def append_value(opts, delim: false, escape: true)
842
+ @session << '--' if delim && !opts.empty?
843
+ opts.each { |val| @session << (escape ? shell_escape(val) : shell_quote(val)) }
838
844
  end
839
845
 
840
- def append_value(opts)
841
- opts.each { |val| @session << shell_escape(val) }
846
+ def append_first(list, flag: true, equals: false, quote: false, **kwargs)
847
+ list.each do |opt|
848
+ next unless (val = option(opt, **kwargs))
849
+
850
+ return @session << (if flag
851
+ shell_option(opt, equals ? val : nil, quote: quote)
852
+ else
853
+ shell_quote(val)
854
+ end)
855
+ end
842
856
  end
843
857
 
844
- def append_option(list, **kwargs)
845
- list.each { |val| @session << "--#{val}" if option(val, **kwargs) }
858
+ def append_option(list, equals: false, quote: false, **kwargs)
859
+ list.each do |flag|
860
+ next unless (val = option(flag, **kwargs))
861
+
862
+ @session << shell_option(flag, equals ? val : nil, quote: quote)
863
+ end
846
864
  end
847
865
 
848
866
  def append_nocolor(flag = nil)
@@ -900,16 +918,17 @@ module Squared
900
918
  ret && !ret.empty? ? ret : [val]
901
919
  end
902
920
 
903
- def pwd_set(done = nil, &blk)
921
+ def pwd_set(done = nil, pass: false, &blk)
904
922
  pwd = Pathname.pwd
905
923
  if block_given?
906
- if path == pwd
907
- instance_eval(&blk)
924
+ if path == pwd || pass == true || (pass.is_a?(String) && semscan(pass).join >= RUBY_VERSION)
925
+ ret = instance_eval(&blk)
908
926
  else
909
927
  Dir.chdir(path)
910
- instance_eval(&blk)
928
+ ret = instance_eval(&blk)
911
929
  Dir.chdir(pwd)
912
930
  end
931
+ ret
913
932
  elsif @pwd == pwd
914
933
  @pwd = nil
915
934
  pwd unless done
@@ -1003,7 +1022,7 @@ module Squared
1003
1022
  end
1004
1023
 
1005
1024
  def variables
1006
- VAR_SET
1025
+ Base.tasks + VAR_SET
1007
1026
  end
1008
1027
 
1009
1028
  def borderstyle
@@ -1013,7 +1032,7 @@ module Squared
1013
1032
  end
1014
1033
 
1015
1034
  def headerstyle
1016
- { pat: /^(\S+)(\s+)$/, styles: theme[:header] }
1035
+ { pat: /\A(\S+)(\s+)\z/, styles: theme[:header] }
1017
1036
  end
1018
1037
 
1019
1038
  def scriptargs
@@ -1021,7 +1040,7 @@ module Squared
1021
1040
  end
1022
1041
  end
1023
1042
 
1024
- Application.impl_project = Base
1043
+ Application.implement(Base, base: true)
1025
1044
  Application.attr_banner = Common::SymSet.new(%i[name project path ref group parent])
1026
1045
  end
1027
1046
  end