squared 0.3.4 → 0.4.0
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 +4 -4
- data/CHANGELOG.md +67 -2
- data/README.md +13 -2
- data/README.ruby.md +168 -88
- data/lib/squared/app.rb +1 -0
- data/lib/squared/common/base.rb +6 -1
- data/lib/squared/common/class.rb +1 -1
- data/lib/squared/common/format.rb +24 -12
- data/lib/squared/common/prompt.rb +2 -2
- data/lib/squared/common/shell.rb +52 -39
- data/lib/squared/common/utils.rb +54 -1
- data/lib/squared/config.rb +1 -1
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/application.rb +16 -13
- data/lib/squared/workspace/project/base.rb +429 -123
- data/lib/squared/workspace/project/docker.rb +572 -0
- data/lib/squared/workspace/project/git.rb +405 -157
- data/lib/squared/workspace/project/node.rb +51 -51
- data/lib/squared/workspace/project/python.rb +115 -24
- data/lib/squared/workspace/project/ruby.rb +33 -34
- data/lib/squared/workspace/project.rb +7 -1
- data/lib/squared/workspace/repo.rb +9 -4
- data/lib/squared/workspace/series.rb +1 -1
- metadata +2 -1
| @@ -15,8 +15,10 @@ module Squared | |
| 15 15 | 
             
                    include Rake::DSL
         | 
| 16 16 |  | 
| 17 17 | 
             
                    VAR_SET = %i[parent global envname dependfile theme run script env pass].freeze
         | 
| 18 | 
            +
                    BLK_SET = %i[run depend doc lint test copy clean].freeze
         | 
| 18 19 | 
             
                    SEM_VER = /\b(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?\b/.freeze
         | 
| 19 | 
            -
                     | 
| 20 | 
            +
                    URI_SCHEME = %r{^([a-z][a-z\d+-.]*)://[^@:\[\]\\^<>|\s]}i.freeze
         | 
| 21 | 
            +
                    private_constant :VAR_SET, :BLK_SET, :SEM_VER, :URI_SCHEME
         | 
| 20 22 |  | 
| 21 23 | 
             
                    class << self
         | 
| 22 24 | 
             
                      def populate(*); end
         | 
| @@ -25,7 +27,7 @@ module Squared | |
| 25 27 | 
             
                      def bannerargs(*); end
         | 
| 26 28 |  | 
| 27 29 | 
             
                      def tasks
         | 
| 28 | 
            -
                        %i[build  | 
| 30 | 
            +
                        (%i[build archive graph] + BLK_SET).freeze
         | 
| 29 31 | 
             
                      end
         | 
| 30 32 |  | 
| 31 33 | 
             
                      def as_path(val)
         | 
| @@ -51,7 +53,8 @@ module Squared | |
| 51 53 | 
             
                    end
         | 
| 52 54 |  | 
| 53 55 | 
             
                    (@@tasks = {})[ref] = {
         | 
| 54 | 
            -
                      'graph' => %i[run print].freeze
         | 
| 56 | 
            +
                      'graph' => %i[run print].freeze,
         | 
| 57 | 
            +
                      'unpack' => %i[zip tar tgz tar.gz txz tar.xz].freeze
         | 
| 55 58 | 
             
                    }.freeze
         | 
| 56 59 | 
             
                    @@task_desc = Rake::TaskManager.record_task_metadata
         | 
| 57 60 | 
             
                    @@print_order = 0
         | 
| @@ -59,8 +62,8 @@ module Squared | |
| 59 62 | 
             
                    attr_reader :name, :project, :workspace, :path, :theme, :exception, :pipe, :verbose,
         | 
| 60 63 | 
             
                                :group, :parent, :dependfile
         | 
| 61 64 |  | 
| 62 | 
            -
                    def initialize(workspace, path, name, *, group: nil, graph: nil, pass: nil, exclude: nil,  | 
| 63 | 
            -
                                   first: {}, last: {}, error: {}, common: ARG[:COMMON], **kwargs)
         | 
| 65 | 
            +
                    def initialize(workspace, path, name, *, group: nil, graph: nil, pass: nil, exclude: nil, release: nil,
         | 
| 66 | 
            +
                                   archive: nil, first: {}, last: {}, error: {}, common: ARG[:COMMON], **kwargs)
         | 
| 64 67 | 
             
                      @path = path
         | 
| 65 68 | 
             
                      @workspace = workspace
         | 
| 66 69 | 
             
                      @name = name.to_s.freeze
         | 
| @@ -73,6 +76,13 @@ module Squared | |
| 73 76 | 
             
                      @copy = kwargs[:copy]
         | 
| 74 77 | 
             
                      @clean = kwargs[:clean]
         | 
| 75 78 | 
             
                      @version = kwargs[:version]
         | 
| 79 | 
            +
                      @archive = case archive
         | 
| 80 | 
            +
                                 when String, Array
         | 
| 81 | 
            +
                                   { uri: archive }
         | 
| 82 | 
            +
                                 when Hash
         | 
| 83 | 
            +
                                   archive
         | 
| 84 | 
            +
                                 end
         | 
| 85 | 
            +
                      @release = release
         | 
| 76 86 | 
             
                      @envname = @name.gsub(/[^\w]+/, '_').upcase.freeze
         | 
| 77 87 | 
             
                      @exception = env_bool(kwargs[:exception], workspace.exception, strict: true)
         | 
| 78 88 | 
             
                      @pipe = env_pipe(kwargs[:pipe], workspace.pipe, strict: true)
         | 
| @@ -108,7 +118,7 @@ module Squared | |
| 108 118 | 
             
                      @parent = nil
         | 
| 109 119 | 
             
                      @global = false
         | 
| 110 120 | 
             
                      run_set(kwargs[:run], kwargs[:env], opts: kwargs.fetch(:opts, true))
         | 
| 111 | 
            -
                      initialize_ref | 
| 121 | 
            +
                      initialize_ref Base.ref
         | 
| 112 122 | 
             
                    end
         | 
| 113 123 |  | 
| 114 124 | 
             
                    def initialize_ref(ref)
         | 
| @@ -116,7 +126,7 @@ module Squared | |
| 116 126 | 
             
                    end
         | 
| 117 127 |  | 
| 118 128 | 
             
                    def initialize_build(ref, **kwargs)
         | 
| 119 | 
            -
                      initialize_ref | 
| 129 | 
            +
                      initialize_ref ref
         | 
| 120 130 | 
             
                      if (@script = @workspace.script_get(group: @group, ref: ref))
         | 
| 121 131 | 
             
                        if @script[:log] && !kwargs.key?(:log)
         | 
| 122 132 | 
             
                          kwargs[:log] = @script[:log]
         | 
| @@ -166,7 +176,9 @@ module Squared | |
| 166 176 | 
             
                    def initialize_events(ref, **)
         | 
| 167 177 | 
             
                      return unless (events = @workspace.events_get(group: @group, ref: ref))
         | 
| 168 178 |  | 
| 169 | 
            -
                      events.each  | 
| 179 | 
            +
                      events.each do |task, data|
         | 
| 180 | 
            +
                        data.each { |ev, blk| (@events[ev] ||= {})[task] ||= [blk] }
         | 
| 181 | 
            +
                      end
         | 
| 170 182 | 
             
                    end
         | 
| 171 183 |  | 
| 172 184 | 
             
                    def initialize_logger(log: nil, **)
         | 
| @@ -192,12 +204,12 @@ module Squared | |
| 192 204 | 
             
                          raise if @exception
         | 
| 193 205 |  | 
| 194 206 | 
             
                          file = nil
         | 
| 195 | 
            -
                          warn log_message(Logger::WARN, e) if warning?
         | 
| 207 | 
            +
                          warn log_message(Logger::WARN, e, pass: true) if warning?
         | 
| 196 208 | 
             
                        end
         | 
| 197 209 | 
             
                      end
         | 
| 198 210 | 
             
                      log[:progname] ||= @name
         | 
| 199 211 | 
             
                      if (val = env('LOG_LEVEL', ignore: false))
         | 
| 200 | 
            -
                        log[:level] = val
         | 
| 212 | 
            +
                        log[:level] = val.match?(/^\d$/) ? log_sym(val.to_i) : val
         | 
| 201 213 | 
             
                      end
         | 
| 202 214 | 
             
                      log.delete(:file)
         | 
| 203 215 | 
             
                      @log = [file, log]
         | 
| @@ -206,20 +218,18 @@ module Squared | |
| 206 218 | 
             
                    def initialize_env(dev: nil, prod: nil, **)
         | 
| 207 219 | 
             
                      @dev = env_match('BUILD', dev, suffix: 'DEV', strict: true)
         | 
| 208 220 | 
             
                      @prod = env_match('BUILD', prod, suffix: 'PROD', strict: true)
         | 
| 209 | 
            -
                       | 
| 210 | 
            -
             | 
| 211 | 
            -
                         | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 214 | 
            -
                         | 
| 215 | 
            -
                           | 
| 216 | 
            -
             | 
| 217 | 
            -
             | 
| 221 | 
            +
                      unless @output[2] == false || !(val = env('BUILD', suffix: 'ENV'))
         | 
| 222 | 
            +
                        data = parse_json(val, hint: "BUILD_#{@envname}_ENV")
         | 
| 223 | 
            +
                        @output[2] = data if data
         | 
| 224 | 
            +
                      end
         | 
| 225 | 
            +
                      unless @output[0] == false || @output[0].is_a?(Array)
         | 
| 226 | 
            +
                        if (val = env('BUILD', suffix: 'OPTS'))
         | 
| 227 | 
            +
                          n = @output[0] ? 1 : 3
         | 
| 228 | 
            +
                          @output[n] = merge_opts(@output[n], shell_split(val, escape: false))
         | 
| 229 | 
            +
                        end
         | 
| 230 | 
            +
                        if (val = env(ref.to_s.upcase, suffix: 'OPTS'))
         | 
| 231 | 
            +
                          @output[4] = merge_opts(@output[4], shell_split(val, escape: false))
         | 
| 218 232 | 
             
                        end
         | 
| 219 | 
            -
                      end
         | 
| 220 | 
            -
                      if cmd != false && !cmd.is_a?(Array)
         | 
| 221 | 
            -
                        @output[cmd ? 1 : 3] = shell_split(val, escape: false, join: true) if (val = env('BUILD', suffix: 'OPTS'))
         | 
| 222 | 
            -
                        @output[4] = shell_split(val, escape: false, join: true) if !cmd && (val = env('SCRIPT', suffix: 'OPTS'))
         | 
| 223 233 | 
             
                      end
         | 
| 224 234 | 
             
                      @version = val if (val = env('BUILD', suffix: 'VERSION'))
         | 
| 225 235 | 
             
                      return unless (val = env('BUILD', strict: true))
         | 
| @@ -261,7 +271,7 @@ module Squared | |
| 261 271 | 
             
                                    out, done = graph(args, out: [])
         | 
| 262 272 | 
             
                                    out.map! do |val|
         | 
| 263 273 | 
             
                                      done.each_with_index do |proj, i|
         | 
| 264 | 
            -
                                        next unless val | 
| 274 | 
            +
                                        next unless val.match?(/ #{Regexp.escape(proj.name)}(?:@\d|\z)/)
         | 
| 265 275 |  | 
| 266 276 | 
             
                                        val += " (#{i.succ})"
         | 
| 267 277 | 
             
                                        break
         | 
| @@ -275,6 +285,25 @@ module Squared | |
| 275 285 | 
             
                                    ])
         | 
| 276 286 | 
             
                                  end
         | 
| 277 287 | 
             
                                end
         | 
| 288 | 
            +
                              when 'unpack'
         | 
| 289 | 
            +
                                format_desc action, flag, 'tag/url,dir,digest?,f/force?'
         | 
| 290 | 
            +
                                task flag, [:tag, :dir, :digest, :force] do |_, args|
         | 
| 291 | 
            +
                                  tag = param_guard(action, flag, args: args, key: :tag)
         | 
| 292 | 
            +
                                  dir = param_guard(action, flag, args: args, key: :dir)
         | 
| 293 | 
            +
                                  unless tag.match?(URI_SCHEME)
         | 
| 294 | 
            +
                                    raise_error 'no base uri' unless @release
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                                    tag = "#{@release.include?('??') ? @release.sub('??', tag) : @release + tag}.#{flag}"
         | 
| 297 | 
            +
                                  end
         | 
| 298 | 
            +
                                  case (digest = args.digest)
         | 
| 299 | 
            +
                                  when 'f', 'force'
         | 
| 300 | 
            +
                                    digest = nil
         | 
| 301 | 
            +
                                    force = true
         | 
| 302 | 
            +
                                  else
         | 
| 303 | 
            +
                                    force = args.fetch(:force, false)
         | 
| 304 | 
            +
                                  end
         | 
| 305 | 
            +
                                  unpack(basepath(dir), uri: tag, digest: digest, ext: flag.to_s, force: force)
         | 
| 306 | 
            +
                                end
         | 
| 278 307 | 
             
                              end
         | 
| 279 308 | 
             
                            end
         | 
| 280 309 | 
             
                          end
         | 
| @@ -343,18 +372,19 @@ module Squared | |
| 343 372 |  | 
| 344 373 | 
             
                      out = obj.link(self, *args, **kwargs, &blk) if obj.respond_to?(:link)
         | 
| 345 374 | 
             
                      if !out
         | 
| 346 | 
            -
                        warn log_message(Logger::WARN, 'link not compatible', subject: obj.to_s, hint: name)
         | 
| 375 | 
            +
                        warn log_message(Logger::WARN, 'link not compatible', subject: obj.to_s, hint: name, pass: true)
         | 
| 347 376 | 
             
                      elsif out.respond_to?(:build)
         | 
| 348 377 | 
             
                        out.build
         | 
| 349 378 | 
             
                      end
         | 
| 350 379 | 
             
                      self
         | 
| 351 380 | 
             
                    end
         | 
| 352 381 |  | 
| 353 | 
            -
                    def build(*args, sync: invoked_sync?('build'), from: : | 
| 382 | 
            +
                    def build(*args, sync: invoked_sync?('build'), from: @buildtype || :build, **)
         | 
| 354 383 | 
             
                      banner = verbose
         | 
| 355 384 | 
             
                      if args.empty?
         | 
| 356 | 
            -
                        return unless from == : | 
| 385 | 
            +
                        return unless from == (@buildtype || :build)
         | 
| 357 386 |  | 
| 387 | 
            +
                        run_b(@run, sync: sync, from: from) if series?(@run)
         | 
| 358 388 | 
             
                        args = @output
         | 
| 359 389 | 
             
                        banner = verbose == 1 if task_invoked?('build', 'build:sync')
         | 
| 360 390 | 
             
                      end
         | 
| @@ -365,7 +395,7 @@ module Squared | |
| 365 395 | 
             
                          a, b, c, d, e = val
         | 
| 366 396 | 
             
                          case b
         | 
| 367 397 | 
             
                          when Hash
         | 
| 368 | 
            -
                            b = append_hash(b).join(' ')
         | 
| 398 | 
            +
                            b = append_hash(b, build: true).join(' ')
         | 
| 369 399 | 
             
                          when Enumerable
         | 
| 370 400 | 
             
                            b = b.to_a.join(' ')
         | 
| 371 401 | 
             
                          end
         | 
| @@ -381,7 +411,7 @@ module Squared | |
| 381 411 | 
             
                        end
         | 
| 382 412 | 
             
                        cmd = cmd.join(' && ')
         | 
| 383 413 | 
             
                      else
         | 
| 384 | 
            -
                        cmd, opts, var, flags,  | 
| 414 | 
            +
                        cmd, opts, var, flags, extra = args
         | 
| 385 415 | 
             
                      end
         | 
| 386 416 | 
             
                      if cmd
         | 
| 387 417 | 
             
                        cmd = as_get(cmd)
         | 
| @@ -389,7 +419,7 @@ module Squared | |
| 389 419 | 
             
                        flags = append_hash(flags).join(' ') if flags.is_a?(Hash)
         | 
| 390 420 | 
             
                        case opts
         | 
| 391 421 | 
             
                        when Hash
         | 
| 392 | 
            -
                          opts = append_hash(opts)
         | 
| 422 | 
            +
                          opts = append_hash(opts, build: true)
         | 
| 393 423 | 
             
                          cmd = as_a(cmd).push(flags).concat(opts).compact.join(' ')
         | 
| 394 424 | 
             
                        when Enumerable
         | 
| 395 425 | 
             
                          cmd = as_a(cmd).concat(opts.to_a)
         | 
| @@ -399,9 +429,9 @@ module Squared | |
| 399 429 | 
             
                          cmd = [cmd, flags, opts].compact.join(' ') if opts || flags
         | 
| 400 430 | 
             
                        end
         | 
| 401 431 | 
             
                      else
         | 
| 402 | 
            -
                        return unless respond_to?(:compose)
         | 
| 432 | 
            +
                        return unless (opts || extra) && respond_to?(:compose)
         | 
| 403 433 |  | 
| 404 | 
            -
                        cmd = compose(as_get(opts), flags, script: true, args:  | 
| 434 | 
            +
                        cmd = compose(as_get(opts), flags, script: true, args: extra, from: from)
         | 
| 405 435 | 
             
                      end
         | 
| 406 436 | 
             
                      run(cmd, var, from: from, banner: banner, sync: sync)
         | 
| 407 437 | 
             
                    end
         | 
| @@ -410,6 +440,12 @@ module Squared | |
| 410 440 | 
             
                      run_b(@depend, sync: sync, from: :depend)
         | 
| 411 441 | 
             
                    end
         | 
| 412 442 |  | 
| 443 | 
            +
                    def archive(*, sync: invoked_sync?('archive'), **)
         | 
| 444 | 
            +
                      return unless @archive.is_a?(Array)
         | 
| 445 | 
            +
             | 
| 446 | 
            +
                      unpack(path, **@archive, sync: sync, from: :archive)
         | 
| 447 | 
            +
                    end
         | 
| 448 | 
            +
             | 
| 413 449 | 
             
                    def doc(*, sync: invoked_sync?('doc'), **)
         | 
| 414 450 | 
             
                      run_b(@doc, sync: sync, from: :doc)
         | 
| 415 451 | 
             
                    end
         | 
| @@ -439,27 +475,31 @@ module Squared | |
| 439 475 | 
             
                        rescue StandardError => e
         | 
| 440 476 | 
             
                          log&.error e
         | 
| 441 477 | 
             
                          ret = on(:error, from, e)
         | 
| 442 | 
            -
                          raise if  | 
| 478 | 
            +
                          raise if exception && ret != true
         | 
| 443 479 | 
             
                        end
         | 
| 444 | 
            -
                       | 
| 445 | 
            -
                         | 
| 446 | 
            -
                           | 
| 447 | 
            -
             | 
| 448 | 
            -
             | 
| 449 | 
            -
                             | 
| 450 | 
            -
             | 
| 451 | 
            -
             | 
| 452 | 
            -
                             | 
| 453 | 
            -
             | 
| 454 | 
            -
                               | 
| 455 | 
            -
             | 
| 456 | 
            -
             | 
| 457 | 
            -
                                 | 
| 458 | 
            -
             | 
| 459 | 
            -
                                 | 
| 480 | 
            +
                      else
         | 
| 481 | 
            +
                        if @clean.is_a?(Enumerable) && !series?(@clean)
         | 
| 482 | 
            +
                          as_a(@clean).each do |val|
         | 
| 483 | 
            +
                            val = val.to_s
         | 
| 484 | 
            +
                            path = basepath(val)
         | 
| 485 | 
            +
                            if path.directory? && val.match?(%r{[\\/]$})
         | 
| 486 | 
            +
                              log&.warn "rm -rf #{path}"
         | 
| 487 | 
            +
                              path.rmtree(verbose: verbose)
         | 
| 488 | 
            +
                            else
         | 
| 489 | 
            +
                              log&.warn "rm #{path}"
         | 
| 490 | 
            +
                              (val.include?('*') ? Dir[path] : [path]).each do |file|
         | 
| 491 | 
            +
                                next unless File.file?(file)
         | 
| 492 | 
            +
             | 
| 493 | 
            +
                                begin
         | 
| 494 | 
            +
                                  File.delete(file)
         | 
| 495 | 
            +
                                rescue StandardError => e
         | 
| 496 | 
            +
                                  log&.error e
         | 
| 497 | 
            +
                                end
         | 
| 460 498 | 
             
                              end
         | 
| 461 499 | 
             
                            end
         | 
| 462 500 | 
             
                          end
         | 
| 501 | 
            +
                        else
         | 
| 502 | 
            +
                          run_b(@clean, sync: sync)
         | 
| 463 503 | 
             
                        end
         | 
| 464 504 | 
             
                      end
         | 
| 465 505 | 
             
                      on :last, :clean
         | 
| @@ -496,6 +536,119 @@ module Squared | |
| 496 536 | 
             
                      end
         | 
| 497 537 | 
             
                    end
         | 
| 498 538 |  | 
| 539 | 
            +
                    def unpack(target, sync: true, uri: nil, digest: nil, ext: nil, force: false, depth: 1, headers: {},
         | 
| 540 | 
            +
                               from: :unpack)
         | 
| 541 | 
            +
                      if !target.exist?
         | 
| 542 | 
            +
                        target.mkpath
         | 
| 543 | 
            +
                      elsif !target.directory?
         | 
| 544 | 
            +
                        raise_error('invalid location', hint: target)
         | 
| 545 | 
            +
                      elsif !target.empty?
         | 
| 546 | 
            +
                        raise_error('directory not empty', hint: target) unless force || env('UNPACK_FORCE')
         | 
| 547 | 
            +
                        create = true
         | 
| 548 | 
            +
                      end
         | 
| 549 | 
            +
                      if digest
         | 
| 550 | 
            +
                        if (n = digest.index(':').to_i) > 0
         | 
| 551 | 
            +
                          size = digest[0, n].downcase
         | 
| 552 | 
            +
                          digest = digest[n + 1..-1]
         | 
| 553 | 
            +
                        else
         | 
| 554 | 
            +
                          size = digest.size
         | 
| 555 | 
            +
                        end
         | 
| 556 | 
            +
                        algo = case size
         | 
| 557 | 
            +
                               when 32, 'md5'
         | 
| 558 | 
            +
                                 Digest::MD5
         | 
| 559 | 
            +
                               when 'rmd160'
         | 
| 560 | 
            +
                                 Digest::RMD160
         | 
| 561 | 
            +
                               when 40, 'sha1'
         | 
| 562 | 
            +
                                 Digest::SHA1
         | 
| 563 | 
            +
                               when 64, 'sha256'
         | 
| 564 | 
            +
                                 Digest::SHA256
         | 
| 565 | 
            +
                               when 96, 'sha384'
         | 
| 566 | 
            +
                                 Digest::SHA384
         | 
| 567 | 
            +
                               when 128, 'sha512'
         | 
| 568 | 
            +
                                 Digest::SHA512
         | 
| 569 | 
            +
                               else
         | 
| 570 | 
            +
                                 raise_error("invalid checksum: #{digest}", hint: name)
         | 
| 571 | 
            +
                               end
         | 
| 572 | 
            +
                      end
         | 
| 573 | 
            +
                      if (val = env('HEADERS')) && (out = parse_json(val, hint: "HEADERS_#{@envname}"))
         | 
| 574 | 
            +
                        headers = out
         | 
| 575 | 
            +
                      end
         | 
| 576 | 
            +
                      require 'open-uri'
         | 
| 577 | 
            +
                      data = nil
         | 
| 578 | 
            +
                      (uri = as_a(uri)).each_with_index do |url, index|
         | 
| 579 | 
            +
                        last = index == uri.size - 1
         | 
| 580 | 
            +
                        URI.open(url, headers) do |f|
         | 
| 581 | 
            +
                          data = f.read
         | 
| 582 | 
            +
                          if algo && algo.hexdigest(data) != digest
         | 
| 583 | 
            +
                            data = nil
         | 
| 584 | 
            +
                            raise_error("checksum failed: #{digest}", hint: url) if last
         | 
| 585 | 
            +
                          end
         | 
| 586 | 
            +
                          next if ext && index == 0
         | 
| 587 | 
            +
             | 
| 588 | 
            +
                          case f.content_type
         | 
| 589 | 
            +
                          when 'application/zip'
         | 
| 590 | 
            +
                            ext = 'zip'
         | 
| 591 | 
            +
                          when 'application/x-gzip'
         | 
| 592 | 
            +
                            ext = 'tgz'
         | 
| 593 | 
            +
                          when 'application/x-xz'
         | 
| 594 | 
            +
                            ext = 'txz'
         | 
| 595 | 
            +
                          end
         | 
| 596 | 
            +
                        end
         | 
| 597 | 
            +
                        if data
         | 
| 598 | 
            +
                          uri = url
         | 
| 599 | 
            +
                          break
         | 
| 600 | 
            +
                        elsif last
         | 
| 601 | 
            +
                          raise_error('no content', hint: url)
         | 
| 602 | 
            +
                        end
         | 
| 603 | 
            +
                      end
         | 
| 604 | 
            +
                      ext ||= URI.parse(uri).path[/^.+?\.((?:tar\.)?\w+)$/i, 1]
         | 
| 605 | 
            +
                      if (n = env("#{ext == 'zip' ? 'ZIP' : 'TAR'}_DEPTH", ignore: false))
         | 
| 606 | 
            +
                        depth = n.to_i
         | 
| 607 | 
            +
                      end
         | 
| 608 | 
            +
                      begin
         | 
| 609 | 
            +
                        require 'tempfile'
         | 
| 610 | 
            +
                        file = Tempfile.new("#{name}-")
         | 
| 611 | 
            +
                        file.write(data)
         | 
| 612 | 
            +
                        file.close
         | 
| 613 | 
            +
                        if create
         | 
| 614 | 
            +
                          warn log_message(Logger::WARN, name, 'force remove', hint: target, pass: true)
         | 
| 615 | 
            +
                          target.rmtree(verbose: true)
         | 
| 616 | 
            +
                          target.mkpath
         | 
| 617 | 
            +
                        end
         | 
| 618 | 
            +
                        case ext
         | 
| 619 | 
            +
                        when 'zip', 'aar'
         | 
| 620 | 
            +
                          session 'unzip', shell_quote(file.path), quote_option('d', target)
         | 
| 621 | 
            +
                        when 'tar', 'tgz', 'tar.gz', 'tar.xz'
         | 
| 622 | 
            +
                          flags = +(verbose ? 'v' : '')
         | 
| 623 | 
            +
                          case ext
         | 
| 624 | 
            +
                          when 'tgz', 'tar.gz'
         | 
| 625 | 
            +
                            flags += 'z'
         | 
| 626 | 
            +
                          when 'txz', 'tar.xz'
         | 
| 627 | 
            +
                            flags += 'J'
         | 
| 628 | 
            +
                          end
         | 
| 629 | 
            +
                          session 'tar', "-x#{flags}", basic_option('strip-components', depth), quote_option('f', file.path),
         | 
| 630 | 
            +
                                  quote_option('C', target)
         | 
| 631 | 
            +
                          depth = 0
         | 
| 632 | 
            +
                        else
         | 
| 633 | 
            +
                          raise_error("unsupported format: #{ext}", hint: uri)
         | 
| 634 | 
            +
                        end
         | 
| 635 | 
            +
                        run(sync: sync, from: from)
         | 
| 636 | 
            +
                        while depth > 0 && target.children.size == 1
         | 
| 637 | 
            +
                          entry = target.children.first
         | 
| 638 | 
            +
                          break unless entry.directory?
         | 
| 639 | 
            +
             | 
| 640 | 
            +
                          dest = target.join(File.basename(file.path))
         | 
| 641 | 
            +
                          FileUtils.mv(entry, dest)
         | 
| 642 | 
            +
                          dest.children.each { |file| FileUtils.mv(file, target) }
         | 
| 643 | 
            +
                          dest.rmdir
         | 
| 644 | 
            +
                          target = entry
         | 
| 645 | 
            +
                          depth -= 1
         | 
| 646 | 
            +
                        end
         | 
| 647 | 
            +
                      ensure
         | 
| 648 | 
            +
                        file&.unlink
         | 
| 649 | 
            +
                      end
         | 
| 650 | 
            +
                    end
         | 
| 651 | 
            +
             | 
| 499 652 | 
             
                    def first(key, *args, **kwargs, &blk)
         | 
| 500 653 | 
             
                      event(:first, key, *args, **kwargs, &blk)
         | 
| 501 654 | 
             
                    end
         | 
| @@ -508,17 +661,38 @@ module Squared | |
| 508 661 | 
             
                      event(:error, key, *args, **kwargs, &blk)
         | 
| 509 662 | 
             
                    end
         | 
| 510 663 |  | 
| 511 | 
            -
                    def event(name, key, *args, **kwargs, &blk)
         | 
| 512 | 
            -
                       | 
| 664 | 
            +
                    def event(name, key, *args, override: false, **kwargs, &blk)
         | 
| 665 | 
            +
                      data = @events[name.to_sym] ||= {}
         | 
| 666 | 
            +
                      items = if override
         | 
| 667 | 
            +
                                data[key.to_sym] = []
         | 
| 668 | 
            +
                              else
         | 
| 669 | 
            +
                                data[key.to_sym] ||= []
         | 
| 670 | 
            +
                              end
         | 
| 671 | 
            +
                      items << [block_given? ? [blk] + args : args, kwargs]
         | 
| 672 | 
            +
                      self
         | 
| 513 673 | 
             
                    end
         | 
| 514 674 |  | 
| 515 675 | 
             
                    def as(cmd, script, to = nil)
         | 
| 516 676 | 
             
                      script = { "#{script}": to } if to
         | 
| 517 677 | 
             
                      data = (@as ||= {})[cmd.to_sym] ||= {}
         | 
| 518 678 | 
             
                      script.each { |key, val| data[key.to_s] = val }
         | 
| 679 | 
            +
                      self
         | 
| 519 680 | 
             
                    end
         | 
| 520 681 |  | 
| 521 | 
            -
                    def  | 
| 682 | 
            +
                    def series(key, override: false, &blk)
         | 
| 683 | 
            +
                      if blocks.include?(key.to_sym) && block_given?
         | 
| 684 | 
            +
                        if !override && series?(target = instance_variable_get(:"@#{key}"))
         | 
| 685 | 
            +
                          target << blk
         | 
| 686 | 
            +
                        else
         | 
| 687 | 
            +
                          instance_variable_set :"@#{key}", [blk]
         | 
| 688 | 
            +
                        end
         | 
| 689 | 
            +
                      else
         | 
| 690 | 
            +
                        log&.warn "series: @#{key} (invalid)"
         | 
| 691 | 
            +
                      end
         | 
| 692 | 
            +
                      self
         | 
| 693 | 
            +
                    end
         | 
| 694 | 
            +
             | 
| 695 | 
            +
                    def variable_set(key, *val, **kwargs, &blk)
         | 
| 522 696 | 
             
                      if variables.include?(key)
         | 
| 523 697 | 
             
                        case key
         | 
| 524 698 | 
             
                        when :build, :run
         | 
| @@ -539,17 +713,22 @@ module Squared | |
| 539 713 | 
             
                        when :dependfile
         | 
| 540 714 | 
             
                          @dependfile = basepath(*val)
         | 
| 541 715 | 
             
                        else
         | 
| 542 | 
            -
                           | 
| 716 | 
            +
                          if blocks.include?(key) && block_given?
         | 
| 717 | 
            +
                            series key, &blk
         | 
| 718 | 
            +
                          else
         | 
| 719 | 
            +
                            instance_variable_set(:"@#{key}", block_given? && val.empty? ? blk : val.first)
         | 
| 720 | 
            +
                          end
         | 
| 543 721 | 
             
                        end
         | 
| 544 722 | 
             
                      else
         | 
| 545 723 | 
             
                        log&.warn "variable_set: @#{key} (private)"
         | 
| 546 724 | 
             
                      end
         | 
| 725 | 
            +
                      self
         | 
| 547 726 | 
             
                    end
         | 
| 548 727 |  | 
| 549 728 | 
             
                    def enabled?(ref = nil, **)
         | 
| 550 729 | 
             
                      return false if ref && !ref?(ref)
         | 
| 551 730 |  | 
| 552 | 
            -
                      path.directory? && !path.empty?
         | 
| 731 | 
            +
                      (path.directory? && !path.empty?) || archive?
         | 
| 553 732 | 
             
                    end
         | 
| 554 733 |  | 
| 555 734 | 
             
                    def has?(meth, ref = nil)
         | 
| @@ -563,7 +742,7 @@ module Squared | |
| 563 742 | 
             
                    end
         | 
| 564 743 |  | 
| 565 744 | 
             
                    def build?
         | 
| 566 | 
            -
                      !!@output[0] || script?
         | 
| 745 | 
            +
                      !!@output[0] || script? || series?(@run)
         | 
| 567 746 | 
             
                    end
         | 
| 568 747 |  | 
| 569 748 | 
             
                    def script?
         | 
| @@ -574,6 +753,10 @@ module Squared | |
| 574 753 | 
             
                      !!@depend
         | 
| 575 754 | 
             
                    end
         | 
| 576 755 |  | 
| 756 | 
            +
                    def archive?
         | 
| 757 | 
            +
                      @archive.is_a?(Hash) && (!path.exist? || path.empty?)
         | 
| 758 | 
            +
                    end
         | 
| 759 | 
            +
             | 
| 577 760 | 
             
                    def graph?
         | 
| 578 761 | 
             
                      @graph.is_a?(Array) && !@graph.empty?
         | 
| 579 762 | 
             
                    end
         | 
| @@ -634,8 +817,12 @@ module Squared | |
| 634 817 | 
             
                      @ref.reverse_each
         | 
| 635 818 | 
             
                    end
         | 
| 636 819 |  | 
| 637 | 
            -
                    def basepath(*args | 
| 638 | 
            -
                       | 
| 820 | 
            +
                    def basepath(*args)
         | 
| 821 | 
            +
                      path.join(*args)
         | 
| 822 | 
            +
                    end
         | 
| 823 | 
            +
             | 
| 824 | 
            +
                    def rootpath(*args, ascend: nil)
         | 
| 825 | 
            +
                      ret = basepath(*args)
         | 
| 639 826 | 
             
                      return ret unless ascend && !ret.exist?
         | 
| 640 827 |  | 
| 641 828 | 
             
                      path.parent.ascend.each do |dir|
         | 
| @@ -672,7 +859,7 @@ module Squared | |
| 672 859 | 
             
                      unless cmd
         | 
| 673 860 | 
             
                        if warning?
         | 
| 674 861 | 
             
                          from &&= from.to_s
         | 
| 675 | 
            -
                          warn log_message(Logger::WARN, from || 'unknown', subject: project, hint: 'no command given')
         | 
| 862 | 
            +
                          warn log_message(Logger::WARN, from || 'unknown', subject: project, hint: 'no command given', pass: true)
         | 
| 676 863 | 
             
                        end
         | 
| 677 864 | 
             
                        return
         | 
| 678 865 | 
             
                      end
         | 
| @@ -709,18 +896,23 @@ module Squared | |
| 709 896 | 
             
                    end
         | 
| 710 897 |  | 
| 711 898 | 
             
                    def run_b(obj, from: nil, sync: true)
         | 
| 712 | 
            -
                       | 
| 713 | 
            -
             | 
| 714 | 
            -
             | 
| 715 | 
            -
                        build(*obj, from: from, sync: sync)
         | 
| 899 | 
            +
                      case obj
         | 
| 900 | 
            +
                      when Proc, Method
         | 
| 901 | 
            +
                        obj.call
         | 
| 716 902 | 
             
                      else
         | 
| 717 | 
            -
                         | 
| 903 | 
            +
                        if series?(obj)
         | 
| 904 | 
            +
                          obj.each(&:call)
         | 
| 905 | 
            +
                        elsif obj.is_a?(Array) && obj.any? { |val| !val.is_a?(String) }
         | 
| 906 | 
            +
                          build(*obj, from: from, sync: sync)
         | 
| 907 | 
            +
                        elsif obj
         | 
| 908 | 
            +
                          run_s(obj.is_a?(Enumerable) ? obj.to_a : obj, from: from, sync: sync)
         | 
| 909 | 
            +
                        end
         | 
| 718 910 | 
             
                      end
         | 
| 719 911 | 
             
                    end
         | 
| 720 912 |  | 
| 721 913 | 
             
                    def graph_branch(target, data, tasks = nil, out = nil, sync: true, pass: [], done: [], depth: 0,
         | 
| 722 914 | 
             
                                     single: false, last: false, context: nil)
         | 
| 723 | 
            -
                      tag = ->(proj) { "#{proj.name}#{SEM_VER | 
| 915 | 
            +
                      tag = ->(proj) { "#{proj.name}#{SEM_VER.match?(proj.version) ? "@#{proj.version}" : ''}" }
         | 
| 724 916 | 
             
                      check = ->(deps) { deps.reject { |val| done.include?(val) } }
         | 
| 725 917 | 
             
                      dedupe = lambda do |name|
         | 
| 726 918 | 
             
                        next [] unless (ret = data[name])
         | 
| @@ -861,8 +1053,7 @@ module Squared | |
| 861 1053 | 
             
                    def session_delete(*list, target: @session)
         | 
| 862 1054 | 
             
                      ret = []
         | 
| 863 1055 | 
             
                      list.each do |val|
         | 
| 864 | 
            -
                         | 
| 865 | 
            -
                        if (key = target.find { |opt| opt =~ pat })
         | 
| 1056 | 
            +
                        if (key = target.find { |opt| opt.match?(/^#{Regexp.escape(shell_option(val))}(?: |=|$)/o) })
         | 
| 866 1057 | 
             
                          target.delete(key)
         | 
| 867 1058 | 
             
                          ret << key
         | 
| 868 1059 | 
             
                        end
         | 
| @@ -877,7 +1068,7 @@ module Squared | |
| 877 1068 | 
             
                    def session_done(cmd)
         | 
| 878 1069 | 
             
                      return cmd unless cmd.respond_to?(:done)
         | 
| 879 1070 |  | 
| 880 | 
            -
                      raise_error('no args  | 
| 1071 | 
            +
                      raise_error('no args added', hint: cmd.first || name) unless cmd.size > 1
         | 
| 881 1072 | 
             
                      @session = nil if cmd == @session
         | 
| 882 1073 | 
             
                      cmd.done
         | 
| 883 1074 | 
             
                    end
         | 
| @@ -898,9 +1089,12 @@ module Squared | |
| 898 1089 | 
             
                      bare = []
         | 
| 899 1090 | 
             
                      e = []
         | 
| 900 1091 | 
             
                      b = []
         | 
| 1092 | 
            +
                      m = []
         | 
| 901 1093 | 
             
                      p = []
         | 
| 902 1094 | 
             
                      q = []
         | 
| 1095 | 
            +
                      qq = []
         | 
| 903 1096 | 
             
                      i = []
         | 
| 1097 | 
            +
                      f = []
         | 
| 904 1098 | 
             
                      list = list.map do |val|
         | 
| 905 1099 | 
             
                        x, y = val.split('|')
         | 
| 906 1100 | 
             
                        if y
         | 
| @@ -920,12 +1114,17 @@ module Squared | |
| 920 1114 | 
             
                            e << flag
         | 
| 921 1115 | 
             
                          when 'b'
         | 
| 922 1116 | 
             
                            b << flag
         | 
| 1117 | 
            +
                          when 'm'
         | 
| 1118 | 
            +
                            m << flag
         | 
| 923 1119 | 
             
                          when 'q'
         | 
| 1120 | 
            +
                            qq << flag if val[n + 2] == 'q'
         | 
| 924 1121 | 
             
                            q << flag
         | 
| 925 1122 | 
             
                          when 'p'
         | 
| 926 1123 | 
             
                            p << flag
         | 
| 927 1124 | 
             
                          when 'i'
         | 
| 928 1125 | 
             
                            i << flag
         | 
| 1126 | 
            +
                          when 'f'
         | 
| 1127 | 
            +
                            f << flag
         | 
| 929 1128 | 
             
                          else
         | 
| 930 1129 | 
             
                            reg << Regexp.escape(flag)
         | 
| 931 1130 | 
             
                          end
         | 
| @@ -946,23 +1145,27 @@ module Squared | |
| 946 1145 | 
             
                          target << "--no-#{name}"
         | 
| 947 1146 | 
             
                        else
         | 
| 948 1147 | 
             
                          if opt =~ /^([^=]+)=(.+)$/
         | 
| 949 | 
            -
                             | 
| 950 | 
            -
                             | 
| 951 | 
            -
             | 
| 952 | 
            -
                             | 
| 953 | 
            -
                              target <<  | 
| 954 | 
            -
                            elsif  | 
| 955 | 
            -
                              target << quote_option( | 
| 956 | 
            -
                            elsif  | 
| 957 | 
            -
                              target <<  | 
| 1148 | 
            +
                            key = $1
         | 
| 1149 | 
            +
                            val = $2
         | 
| 1150 | 
            +
                            match = ->(flag, pat) { flag.include?(key) && pat.match?(val) }
         | 
| 1151 | 
            +
                            if e.include?(key)
         | 
| 1152 | 
            +
                              target << shell_option(key, val)
         | 
| 1153 | 
            +
                            elsif q.include?(key)
         | 
| 1154 | 
            +
                              target << quote_option(key, val, double: qq.include?(key))
         | 
| 1155 | 
            +
                            elsif p.include?(key)
         | 
| 1156 | 
            +
                              target << quote_option(key, basepath(val))
         | 
| 1157 | 
            +
                            elsif m.include?(key)
         | 
| 1158 | 
            +
                              target << basic_option(key, val, merge: true)
         | 
| 1159 | 
            +
                            elsif b.include?(key) || match.(i, /^\d+$/) || match.(f, /^\d*(?:\.\d+)?$/)
         | 
| 1160 | 
            +
                              target << basic_option(key, val)
         | 
| 958 1161 | 
             
                            else
         | 
| 959 1162 | 
             
                              ret << opt
         | 
| 960 1163 | 
             
                            end
         | 
| 961 | 
            -
                            opt =  | 
| 1164 | 
            +
                            opt = key
         | 
| 962 1165 | 
             
                          else
         | 
| 963 1166 | 
             
                            ret << opt
         | 
| 964 1167 | 
             
                          end
         | 
| 965 | 
            -
                          found = true if first && pass.none? { | | 
| 1168 | 
            +
                          found = true if first && pass.none? { |s| opt.include?(s) }
         | 
| 966 1169 | 
             
                        end
         | 
| 967 1170 | 
             
                      end
         | 
| 968 1171 | 
             
                      [ret, reg.empty? ? /\A\s+\z/ : /^(#{reg.join('|')})=(.+)$/]
         | 
| @@ -971,7 +1174,7 @@ module Squared | |
| 971 1174 | 
             
                    def option_clear(opts, target: @session, **kwargs)
         | 
| 972 1175 | 
             
                      kwargs[:subject] ||= target&.first
         | 
| 973 1176 | 
             
                      kwargs[:hint] ||= 'not used'
         | 
| 974 | 
            -
                      warn log_message(Logger::WARN, opts.join(', '), **kwargs) unless opts.empty?
         | 
| 1177 | 
            +
                      warn log_message(Logger::WARN, opts.join(', '), pass: true, **kwargs) unless opts.empty?
         | 
| 975 1178 | 
             
                    end
         | 
| 976 1179 |  | 
| 977 1180 | 
             
                    def print_item(*val)
         | 
| @@ -1001,7 +1204,7 @@ module Squared | |
| 1001 1204 | 
             
                          val
         | 
| 1002 1205 | 
             
                        end
         | 
| 1003 1206 | 
             
                      end
         | 
| 1004 | 
            -
                      out << sub_style( | 
| 1207 | 
            +
                      out << sub_style(ARG[:BORDER][1] * n, styles: border)
         | 
| 1005 1208 | 
             
                      out.join("\n")
         | 
| 1006 1209 | 
             
                    end
         | 
| 1007 1210 |  | 
| @@ -1013,7 +1216,7 @@ module Squared | |
| 1013 1216 | 
             
                        sub.each { |h| s = sub_style(s, **h) }
         | 
| 1014 1217 | 
             
                        s
         | 
| 1015 1218 | 
             
                      end
         | 
| 1016 | 
            -
                      ret = [sub_style( | 
| 1219 | 
            +
                      ret = [sub_style(ARG[:BORDER][1] * n, styles: kwargs.key?(:border) ? kwargs[:border] : borderstyle), *lines]
         | 
| 1017 1220 | 
             
                      ret.reverse! if reverse
         | 
| 1018 1221 | 
             
                      ret.join("\n")
         | 
| 1019 1222 | 
             
                    end
         | 
| @@ -1039,7 +1242,7 @@ module Squared | |
| 1039 1242 | 
             
                      if verbose
         | 
| 1040 1243 | 
             
                        out = []
         | 
| 1041 1244 | 
             
                        if data[:command]
         | 
| 1042 | 
            -
                          if /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+))  | 
| 1245 | 
            +
                          if cmd =~ /\A(?:"((?:[^"]|(?<=\\)")+)"|'((?:[^']|(?<=\\)')+)'|(\S+)) /
         | 
| 1043 1246 | 
             
                            path = $3 || $2 || $1
         | 
| 1044 1247 | 
             
                            cmd = cmd.sub(path, File.basename(path).upcase)
         | 
| 1045 1248 | 
             
                          end
         | 
| @@ -1077,9 +1280,9 @@ module Squared | |
| 1077 1280 | 
             
                      unless items.empty?
         | 
| 1078 1281 | 
             
                        pad = items.size.to_s.size
         | 
| 1079 1282 | 
             
                        items.each_with_index do |val, i|
         | 
| 1080 | 
            -
                          next unless reg.empty? || reg.any? { |pat| val[0] | 
| 1283 | 
            +
                          next unless reg.empty? || reg.any? { |pat| val[0].match?(pat) }
         | 
| 1081 1284 |  | 
| 1082 | 
            -
                          out << "#{ | 
| 1285 | 
            +
                          out << "#{i.succ.to_s.rjust(pad)}. #{each ? each.(val) : val[0]}"
         | 
| 1083 1286 | 
             
                        end
         | 
| 1084 1287 | 
             
                      end
         | 
| 1085 1288 | 
             
                      sub = [headerstyle]
         | 
| @@ -1113,9 +1316,9 @@ module Squared | |
| 1113 1316 | 
             
                      opts.each { |val| target << shell_option(flag, val) }
         | 
| 1114 1317 | 
             
                    end
         | 
| 1115 1318 |  | 
| 1116 | 
            -
                    def append_hash(data, target: @session)
         | 
| 1319 | 
            +
                    def append_hash(data, target: @session, build: false)
         | 
| 1117 1320 | 
             
                      target ||= []
         | 
| 1118 | 
            -
                      if (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
         | 
| 1321 | 
            +
                      if build && (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
         | 
| 1119 1322 | 
             
                        type = "__#{type}__"
         | 
| 1120 1323 | 
             
                        if (extra = data[type] || data[type.to_sym]).is_a?(Hash)
         | 
| 1121 1324 | 
             
                          data = data.merge(extra)
         | 
| @@ -1144,7 +1347,7 @@ module Squared | |
| 1144 1347 | 
             
                      target
         | 
| 1145 1348 | 
             
                    end
         | 
| 1146 1349 |  | 
| 1147 | 
            -
                    def append_any(val, target: @session, delim: false)
         | 
| 1350 | 
            +
                    def append_any(val, target: @session, build: false, delim: false)
         | 
| 1148 1351 | 
             
                      if delim && !target.include?('--')
         | 
| 1149 1352 | 
             
                        target << '--'
         | 
| 1150 1353 | 
             
                      else
         | 
| @@ -1154,7 +1357,7 @@ module Squared | |
| 1154 1357 | 
             
                      when String
         | 
| 1155 1358 | 
             
                        target << val
         | 
| 1156 1359 | 
             
                      when Hash
         | 
| 1157 | 
            -
                        append_hash(val, target: target)
         | 
| 1360 | 
            +
                        append_hash(val, target: target, build: build)
         | 
| 1158 1361 | 
             
                      when Enumerable
         | 
| 1159 1362 | 
             
                        if target.is_a?(Array)
         | 
| 1160 1363 | 
             
                          target.concat(val.to_a)
         | 
| @@ -1163,6 +1366,7 @@ module Squared | |
| 1163 1366 | 
             
                        end
         | 
| 1164 1367 | 
             
                      else
         | 
| 1165 1368 | 
             
                        target.delete('--') if delim
         | 
| 1369 | 
            +
                        nil
         | 
| 1166 1370 | 
             
                      end
         | 
| 1167 1371 | 
             
                    end
         | 
| 1168 1372 |  | 
| @@ -1171,8 +1375,9 @@ module Squared | |
| 1171 1375 |  | 
| 1172 1376 | 
             
                      target << '--' if delim && !target.include?('--')
         | 
| 1173 1377 | 
             
                      list.map do |val|
         | 
| 1174 | 
            -
                         | 
| 1175 | 
            -
                         | 
| 1378 | 
            +
                        item = escape ? shell_escape(val, quote: quote) : shell_quote(val)
         | 
| 1379 | 
            +
                        target << item
         | 
| 1380 | 
            +
                        item
         | 
| 1176 1381 | 
             
                      end
         | 
| 1177 1382 | 
             
                    end
         | 
| 1178 1383 |  | 
| @@ -1180,7 +1385,7 @@ module Squared | |
| 1180 1385 | 
             
                                     **kwargs)
         | 
| 1181 1386 | 
             
                      return if (list = list.flatten).empty?
         | 
| 1182 1387 |  | 
| 1183 | 
            -
                      list. | 
| 1388 | 
            +
                      list.each do |opt|
         | 
| 1184 1389 | 
             
                        next unless (val = option(opt, **kwargs))
         | 
| 1185 1390 |  | 
| 1186 1391 | 
             
                        return target << (if flag
         | 
| @@ -1197,7 +1402,7 @@ module Squared | |
| 1197 1402 | 
             
                      return if (list = list.flatten).empty?
         | 
| 1198 1403 |  | 
| 1199 1404 | 
             
                      ret = []
         | 
| 1200 | 
            -
                      list. | 
| 1405 | 
            +
                      list.each do |flag|
         | 
| 1201 1406 | 
             
                        next unless (val = option(flag, **kwargs))
         | 
| 1202 1407 |  | 
| 1203 1408 | 
             
                        if val == '0' && no
         | 
| @@ -1213,16 +1418,63 @@ module Squared | |
| 1213 1418 | 
             
                      target << '--no-color' if !ARG[:COLOR] || stdin? || option('no-color', ignore: false)
         | 
| 1214 1419 | 
             
                    end
         | 
| 1215 1420 |  | 
| 1421 | 
            +
                    def merge_opts(base, data)
         | 
| 1422 | 
            +
                      return data unless base
         | 
| 1423 | 
            +
                      return base unless data
         | 
| 1424 | 
            +
             | 
| 1425 | 
            +
                      ret = case data
         | 
| 1426 | 
            +
                            when String
         | 
| 1427 | 
            +
                              case base
         | 
| 1428 | 
            +
                              when String
         | 
| 1429 | 
            +
                                "#{base} #{data}"
         | 
| 1430 | 
            +
                              when Hash
         | 
| 1431 | 
            +
                                "#{append_hash(base).join(' ')} #{data}"
         | 
| 1432 | 
            +
                              when Enumerable
         | 
| 1433 | 
            +
                                "#{base.to_a.join(' ')} #{data}"
         | 
| 1434 | 
            +
                              end
         | 
| 1435 | 
            +
                            when Hash
         | 
| 1436 | 
            +
                              case base
         | 
| 1437 | 
            +
                              when String
         | 
| 1438 | 
            +
                                "#{base} #{append_hash(data).join(' ')}"
         | 
| 1439 | 
            +
                              when Hash
         | 
| 1440 | 
            +
                                base.merge(data)
         | 
| 1441 | 
            +
                              when Enumerable
         | 
| 1442 | 
            +
                                base.to_a + append_hash(data)
         | 
| 1443 | 
            +
                              end
         | 
| 1444 | 
            +
                            when Enumerable
         | 
| 1445 | 
            +
                              case base
         | 
| 1446 | 
            +
                              when String
         | 
| 1447 | 
            +
                                "#{base} #{data.to_a.join(' ')}"
         | 
| 1448 | 
            +
                              when Hash
         | 
| 1449 | 
            +
                                "#{append_hash(base).join(' ')} #{data.to_a.join(' ')}"
         | 
| 1450 | 
            +
                              when Enumerable
         | 
| 1451 | 
            +
                                base.to_a + data.to_a
         | 
| 1452 | 
            +
                              end
         | 
| 1453 | 
            +
                            else
         | 
| 1454 | 
            +
                              base
         | 
| 1455 | 
            +
                            end
         | 
| 1456 | 
            +
                      ret || data
         | 
| 1457 | 
            +
                    end
         | 
| 1458 | 
            +
             | 
| 1216 1459 | 
             
                    def collect_hash(data, pass: [])
         | 
| 1217 1460 | 
             
                      ret = []
         | 
| 1218 1461 | 
             
                      data.each { |key, val| ret += val unless pass.include?(key) }
         | 
| 1219 1462 | 
             
                      ret
         | 
| 1220 1463 | 
             
                    end
         | 
| 1221 1464 |  | 
| 1222 | 
            -
                    def  | 
| 1465 | 
            +
                    def parse_json(val, hint: nil)
         | 
| 1466 | 
            +
                      ret = JSON.parse(val)
         | 
| 1467 | 
            +
                      raise_error('invalid JSON object', val, hint: hint) unless ret.is_a?(Hash)
         | 
| 1468 | 
            +
                    rescue StandardError => e
         | 
| 1469 | 
            +
                      log&.warn e
         | 
| 1470 | 
            +
                    else
         | 
| 1471 | 
            +
                      ret
         | 
| 1472 | 
            +
                    end
         | 
| 1473 | 
            +
             | 
| 1474 | 
            +
                    def param_guard(action, flag, args: nil, key: nil, pat: nil, values: nil)
         | 
| 1223 1475 | 
             
                      if args && key
         | 
| 1224 1476 | 
             
                        val = args[key]
         | 
| 1225 | 
            -
                        return val unless val.nil? || (pat && !val.match?(pat))
         | 
| 1477 | 
            +
                        return val unless val.nil? || (pat && !val.match?(pat)) || (values && !values.include?(val))
         | 
| 1226 1478 |  | 
| 1227 1479 | 
             
                        @session = nil
         | 
| 1228 1480 | 
             
                        raise_error(action, "#{flag}[#{key}]", hint: val.nil? ? 'missing' : 'invalid')
         | 
| @@ -1233,6 +1485,17 @@ module Squared | |
| 1233 1485 | 
             
                      args
         | 
| 1234 1486 | 
             
                    end
         | 
| 1235 1487 |  | 
| 1488 | 
            +
                    def relativepath(*files, all: false)
         | 
| 1489 | 
            +
                      return [] if files.empty?
         | 
| 1490 | 
            +
             | 
| 1491 | 
            +
                      files.flatten.map { |val| Pathname.new(val) }.select { |val| projectpath?(val) }.map do |val|
         | 
| 1492 | 
            +
                        val = val.absolute? ? val.to_s.sub(/^#{Regexp.escape(File.join(path, ''))}/o, '') : val.to_s
         | 
| 1493 | 
            +
                        val = val[2..-1] if val.start_with?('./')
         | 
| 1494 | 
            +
                        val = "#{val}*" if all && val.end_with?('/')
         | 
| 1495 | 
            +
                        val
         | 
| 1496 | 
            +
                      end
         | 
| 1497 | 
            +
                    end
         | 
| 1498 | 
            +
             | 
| 1236 1499 | 
             
                    def projectmap(files, parent: false)
         | 
| 1237 1500 | 
             
                      files = files.select { |val| projectpath?(val) } unless parent
         | 
| 1238 1501 | 
             
                      files.map { |val| val == '.' ? '.' : shell_quote(basepath(val.strip)) }
         | 
| @@ -1274,22 +1537,28 @@ module Squared | |
| 1274 1537 | 
             
                      val.compact.map { |s| color(s) }.flatten
         | 
| 1275 1538 | 
             
                    end
         | 
| 1276 1539 |  | 
| 1540 | 
            +
                    def epochtime
         | 
| 1541 | 
            +
                      DateTime.now.strftime('%Q').to_i
         | 
| 1542 | 
            +
                    end
         | 
| 1543 | 
            +
             | 
| 1277 1544 | 
             
                    def on(event, from, *args, **kwargs)
         | 
| 1278 | 
            -
                      return unless from | 
| 1545 | 
            +
                      return unless from
         | 
| 1279 1546 |  | 
| 1280 | 
            -
                       | 
| 1281 | 
            -
                         | 
| 1282 | 
            -
             | 
| 1283 | 
            -
             | 
| 1284 | 
            -
                         | 
| 1285 | 
            -
             | 
| 1286 | 
            -
             | 
| 1287 | 
            -
             | 
| 1288 | 
            -
                         | 
| 1289 | 
            -
             | 
| 1290 | 
            -
                           | 
| 1291 | 
            -
             | 
| 1292 | 
            -
                           | 
| 1547 | 
            +
                      @events[event][from]&.each do |obj|
         | 
| 1548 | 
            +
                        if obj.is_a?(Array) && obj[1].is_a?(Hash)
         | 
| 1549 | 
            +
                          opts = kwargs.empty? ? obj[1] : obj[1].merge(kwargs)
         | 
| 1550 | 
            +
                          target = obj[0]
         | 
| 1551 | 
            +
                        else
         | 
| 1552 | 
            +
                          opts = kwargs
         | 
| 1553 | 
            +
                          target = obj
         | 
| 1554 | 
            +
                        end
         | 
| 1555 | 
            +
                        as_a(target, flat: true).each do |cmd|
         | 
| 1556 | 
            +
                          case cmd
         | 
| 1557 | 
            +
                          when Proc, Method
         | 
| 1558 | 
            +
                            cmd.call(*args, **opts)
         | 
| 1559 | 
            +
                          when String
         | 
| 1560 | 
            +
                            run(cmd, **opts)
         | 
| 1561 | 
            +
                          end
         | 
| 1293 1562 | 
             
                        end
         | 
| 1294 1563 | 
             
                      end
         | 
| 1295 1564 | 
             
                    end
         | 
| @@ -1330,23 +1599,53 @@ module Squared | |
| 1330 1599 | 
             
                    end
         | 
| 1331 1600 |  | 
| 1332 1601 | 
             
                    def run_set(cmd, val = nil, opts: nil, **)
         | 
| 1333 | 
            -
                       | 
| 1334 | 
            -
             | 
| 1335 | 
            -
                       | 
| 1602 | 
            +
                      diso = @output[1] == false && !@output[0].nil?
         | 
| 1603 | 
            +
                      dise = @output[2] == false
         | 
| 1604 | 
            +
                      parse = lambda do |data|
         | 
| 1605 | 
            +
                        ret = []
         | 
| 1606 | 
            +
                        if data[:command]
         | 
| 1607 | 
            +
                          ret[0] = data[:command]
         | 
| 1608 | 
            +
                          ret[1] = data[:opts] unless diso
         | 
| 1609 | 
            +
                          ret[3] = data[:args]
         | 
| 1610 | 
            +
                        elsif data[:script]
         | 
| 1611 | 
            +
                          ret[1] = data[:script]
         | 
| 1612 | 
            +
                          ret[3] = data[:opts]
         | 
| 1613 | 
            +
                          ret[4] = data[:args]
         | 
| 1614 | 
            +
                        else
         | 
| 1615 | 
            +
                          ret[0] = false
         | 
| 1616 | 
            +
                        end
         | 
| 1617 | 
            +
                        ret[2] = data[:env] unless dise
         | 
| 1618 | 
            +
                        ret
         | 
| 1619 | 
            +
                      end
         | 
| 1620 | 
            +
                      case cmd
         | 
| 1621 | 
            +
                      when Array
         | 
| 1622 | 
            +
                        @output = if cmd.all? { |data| data.is_a?(Hash) }
         | 
| 1623 | 
            +
                                    diso = false
         | 
| 1624 | 
            +
                                    dise = false
         | 
| 1625 | 
            +
                                    cmd.map { |data| parse.(data) }
         | 
| 1626 | 
            +
                                  else
         | 
| 1627 | 
            +
                                    cmd.dup
         | 
| 1628 | 
            +
                                  end
         | 
| 1629 | 
            +
                        return
         | 
| 1630 | 
            +
                      when Hash
         | 
| 1631 | 
            +
                        @output = parse.(data)
         | 
| 1632 | 
            +
                      else
         | 
| 1633 | 
            +
                        @output[0] = cmd
         | 
| 1634 | 
            +
                      end
         | 
| 1635 | 
            +
                      unless diso
         | 
| 1336 1636 | 
             
                        if opts == false
         | 
| 1337 1637 | 
             
                          @output[1] = false
         | 
| 1338 1638 | 
             
                        elsif opts && opts != true
         | 
| 1339 1639 | 
             
                          @output[1] = opts
         | 
| 1340 1640 | 
             
                        end
         | 
| 1341 1641 | 
             
                      end
         | 
| 1342 | 
            -
                       | 
| 1343 | 
            -
             | 
| 1344 | 
            -
             | 
| 1345 | 
            -
                         | 
| 1346 | 
            -
             | 
| 1347 | 
            -
                         | 
| 1642 | 
            +
                      return if dise
         | 
| 1643 | 
            +
             | 
| 1644 | 
            +
                      if val.is_a?(Hash)
         | 
| 1645 | 
            +
                        @output[2] = val
         | 
| 1646 | 
            +
                      elsif val == false
         | 
| 1647 | 
            +
                        @output[2] = false
         | 
| 1348 1648 | 
             
                      end
         | 
| 1349 | 
            -
                      @output[0] = cmd
         | 
| 1350 1649 | 
             
                    end
         | 
| 1351 1650 |  | 
| 1352 1651 | 
             
                    def script_set(cmd, prod: nil, args: nil, **)
         | 
| @@ -1362,6 +1661,8 @@ module Squared | |
| 1362 1661 | 
             
                    end
         | 
| 1363 1662 |  | 
| 1364 1663 | 
             
                    def as_get(val)
         | 
| 1664 | 
            +
                      return unless val
         | 
| 1665 | 
            +
             | 
| 1365 1666 | 
             
                      @global && (ret = @as && @as[from] && @as[from][val]) ? ret : val
         | 
| 1366 1667 | 
             
                    end
         | 
| 1367 1668 |  | 
| @@ -1396,19 +1697,20 @@ module Squared | |
| 1396 1697 |  | 
| 1397 1698 | 
             
                    def runnable?(val)
         | 
| 1398 1699 | 
             
                      case val
         | 
| 1399 | 
            -
                      when String
         | 
| 1700 | 
            +
                      when String, Enumerable, Proc, Method
         | 
| 1400 1701 | 
             
                        true
         | 
| 1401 | 
            -
                      when Enumerable
         | 
| 1402 | 
            -
                        !val.is_a?(Hash)
         | 
| 1403 1702 | 
             
                      else
         | 
| 1404 1703 | 
             
                        false
         | 
| 1405 1704 | 
             
                      end
         | 
| 1406 1705 | 
             
                    end
         | 
| 1407 1706 |  | 
| 1707 | 
            +
                    def series?(val)
         | 
| 1708 | 
            +
                      val.is_a?(Array) && val.all? { |p| p.is_a?(Proc) || p.is_a?(Method) }
         | 
| 1709 | 
            +
                    end
         | 
| 1710 | 
            +
             | 
| 1408 1711 | 
             
                    def session_arg?(*list, target: @session, value: false)
         | 
| 1409 1712 | 
             
                      list.any? do |val|
         | 
| 1410 | 
            -
                         | 
| 1411 | 
            -
                        target.any? { |opt| opt =~ pat }
         | 
| 1713 | 
            +
                        target.any? { |opt| opt.match?(/^#{Regexp.escape(shell_option(val))}#{value ? '[ =].' : '(?:[ =]|$)'}/o) }
         | 
| 1412 1714 | 
             
                      end
         | 
| 1413 1715 | 
             
                    end
         | 
| 1414 1716 |  | 
| @@ -1445,6 +1747,10 @@ module Squared | |
| 1445 1747 | 
             
                      Base.tasks + VAR_SET
         | 
| 1446 1748 | 
             
                    end
         | 
| 1447 1749 |  | 
| 1750 | 
            +
                    def blocks
         | 
| 1751 | 
            +
                      BLK_SET
         | 
| 1752 | 
            +
                    end
         | 
| 1753 | 
            +
             | 
| 1448 1754 | 
             
                    def borderstyle
         | 
| 1449 1755 | 
             
                      ((data = workspace.banner_get(*@ref, group: group)) && data[:border]) || theme[:border]
         | 
| 1450 1756 | 
             
                    end
         |