toys-core 0.9.4 → 0.10.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/.yardopts +2 -1
- data/CHANGELOG.md +30 -0
- data/LICENSE.md +1 -1
- data/README.md +3 -3
- data/lib/toys-core.rb +11 -21
- data/lib/toys/acceptor.rb +0 -21
- data/lib/toys/arg_parser.rb +1 -22
- data/lib/toys/cli.rb +102 -70
- data/lib/toys/compat.rb +49 -41
- data/lib/toys/completion.rb +0 -21
- data/lib/toys/context.rb +0 -23
- data/lib/toys/core.rb +1 -22
- data/lib/toys/dsl/flag.rb +0 -21
- data/lib/toys/dsl/flag_group.rb +0 -21
- data/lib/toys/dsl/positional_arg.rb +0 -21
- data/lib/toys/dsl/tool.rb +135 -51
- data/lib/toys/errors.rb +0 -21
- data/lib/toys/flag.rb +0 -21
- data/lib/toys/flag_group.rb +0 -21
- data/lib/toys/input_file.rb +0 -21
- data/lib/toys/loader.rb +41 -78
- data/lib/toys/middleware.rb +146 -77
- data/lib/toys/mixin.rb +0 -21
- data/lib/toys/module_lookup.rb +3 -26
- data/lib/toys/positional_arg.rb +0 -21
- data/lib/toys/source_info.rb +49 -38
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +0 -23
- data/lib/toys/standard_middleware/apply_config.rb +42 -0
- data/lib/toys/standard_middleware/handle_usage_errors.rb +7 -28
- data/lib/toys/standard_middleware/set_default_descriptions.rb +0 -23
- data/lib/toys/standard_middleware/show_help.rb +0 -23
- data/lib/toys/standard_middleware/show_root_version.rb +0 -23
- data/lib/toys/standard_mixins/bundler.rb +89 -0
- data/lib/toys/standard_mixins/exec.rb +124 -35
- data/lib/toys/standard_mixins/fileutils.rb +0 -21
- data/lib/toys/standard_mixins/gems.rb +2 -24
- data/lib/toys/standard_mixins/highline.rb +0 -21
- data/lib/toys/standard_mixins/terminal.rb +0 -21
- data/lib/toys/template.rb +0 -21
- data/lib/toys/tool.rb +22 -34
- data/lib/toys/utils/completion_engine.rb +0 -21
- data/lib/toys/utils/exec.rb +1 -21
- data/lib/toys/utils/gems.rb +174 -63
- data/lib/toys/utils/help_text.rb +0 -21
- data/lib/toys/utils/terminal.rb +46 -37
- data/lib/toys/wrappable_string.rb +0 -21
- metadata +25 -9
| @@ -1,26 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            # Copyright 2019 Daniel Azuma
         | 
| 4 | 
            -
            #
         | 
| 5 | 
            -
            # Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            -
            # of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            -
            # in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            -
            # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            -
            # copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            -
            # furnished to do so, subject to the following conditions:
         | 
| 11 | 
            -
            #
         | 
| 12 | 
            -
            # The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            -
            # all copies or substantial portions of the Software.
         | 
| 14 | 
            -
            #
         | 
| 15 | 
            -
            # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            -
            # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            -
            # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            -
            # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            -
            # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
         | 
| 20 | 
            -
            # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
         | 
| 21 | 
            -
            # IN THE SOFTWARE.
         | 
| 22 | 
            -
            ;
         | 
| 23 | 
            -
             | 
| 24 3 | 
             
            module Toys
         | 
| 25 4 | 
             
              module StandardMixins
         | 
| 26 5 | 
             
                ##
         | 
    
        data/lib/toys/template.rb
    CHANGED
    
    | @@ -1,26 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            # Copyright 2019 Daniel Azuma
         | 
| 4 | 
            -
            #
         | 
| 5 | 
            -
            # Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            -
            # of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            -
            # in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            -
            # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            -
            # copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            -
            # furnished to do so, subject to the following conditions:
         | 
| 11 | 
            -
            #
         | 
| 12 | 
            -
            # The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            -
            # all copies or substantial portions of the Software.
         | 
| 14 | 
            -
            #
         | 
| 15 | 
            -
            # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            -
            # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            -
            # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            -
            # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            -
            # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
         | 
| 20 | 
            -
            # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
         | 
| 21 | 
            -
            # IN THE SOFTWARE.
         | 
| 22 | 
            -
            ;
         | 
| 23 | 
            -
             | 
| 24 3 | 
             
            module Toys
         | 
| 25 4 | 
             
              ##
         | 
| 26 5 | 
             
              # A template definition. Template classes should include this module.
         | 
    
        data/lib/toys/tool.rb
    CHANGED
    
    | @@ -1,26 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            # Copyright 2019 Daniel Azuma
         | 
| 4 | 
            -
            #
         | 
| 5 | 
            -
            # Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            -
            # of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            -
            # in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            -
            # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            -
            # copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            -
            # furnished to do so, subject to the following conditions:
         | 
| 11 | 
            -
            #
         | 
| 12 | 
            -
            # The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            -
            # all copies or substantial portions of the Software.
         | 
| 14 | 
            -
            #
         | 
| 15 | 
            -
            # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            -
            # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            -
            # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            -
            # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            -
            # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
         | 
| 20 | 
            -
            # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
         | 
| 21 | 
            -
            # IN THE SOFTWARE.
         | 
| 22 | 
            -
            ;
         | 
| 23 | 
            -
             | 
| 24 3 | 
             
            require "set"
         | 
| 25 4 |  | 
| 26 5 | 
             
            module Toys
         | 
| @@ -37,11 +16,12 @@ module Toys | |
| 37 16 | 
             
                # Should be created only from the DSL via the Loader.
         | 
| 38 17 | 
             
                # @private
         | 
| 39 18 | 
             
                #
         | 
| 40 | 
            -
                def initialize(loader, parent, full_name, priority, middleware_stack)
         | 
| 19 | 
            +
                def initialize(loader, parent, full_name, priority, middleware_stack, middleware_lookup)
         | 
| 41 20 | 
             
                  @parent = parent
         | 
| 42 21 | 
             
                  @full_name = full_name.dup.freeze
         | 
| 43 22 | 
             
                  @priority = priority
         | 
| 44 | 
            -
                  @ | 
| 23 | 
            +
                  @built_middleware = middleware_stack.build(middleware_lookup)
         | 
| 24 | 
            +
                  @subtool_middleware_stack = middleware_stack.dup
         | 
| 45 25 |  | 
| 46 26 | 
             
                  @acceptors = {}
         | 
| 47 27 | 
             
                  @mixins = {}
         | 
| @@ -201,11 +181,20 @@ module Toys | |
| 201 181 | 
             
                attr_reader :default_data
         | 
| 202 182 |  | 
| 203 183 | 
             
                ##
         | 
| 204 | 
            -
                # The middleware  | 
| 184 | 
            +
                # The stack of middleware specs used for subtools.
         | 
| 185 | 
            +
                #
         | 
| 186 | 
            +
                # This array may be modified in place.
         | 
| 187 | 
            +
                #
         | 
| 188 | 
            +
                # @return [Array<Toys::Middleware::Spec>]
         | 
| 189 | 
            +
                #
         | 
| 190 | 
            +
                attr_reader :subtool_middleware_stack
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                ##
         | 
| 193 | 
            +
                # The stack of built middleware specs for this tool.
         | 
| 205 194 | 
             
                #
         | 
| 206 195 | 
             
                # @return [Array<Toys::Middleware>]
         | 
| 207 196 | 
             
                #
         | 
| 208 | 
            -
                attr_reader : | 
| 197 | 
            +
                attr_reader :built_middleware
         | 
| 209 198 |  | 
| 210 199 | 
             
                ##
         | 
| 211 200 | 
             
                # Info on the source of this tool.
         | 
| @@ -473,18 +462,13 @@ module Toys | |
| 473 462 | 
             
                ##
         | 
| 474 463 | 
             
                # Sets the path to the file that defines this tool.
         | 
| 475 464 | 
             
                # A tool may be defined from at most one path. If a different path is
         | 
| 476 | 
            -
                # already set,  | 
| 465 | 
            +
                # already set, it is left unchanged.
         | 
| 477 466 | 
             
                #
         | 
| 478 467 | 
             
                # @param source [Toys::SourceInfo] Source info
         | 
| 479 468 | 
             
                # @return [self]
         | 
| 480 469 | 
             
                #
         | 
| 481 470 | 
             
                def lock_source(source)
         | 
| 482 | 
            -
                   | 
| 483 | 
            -
                    raise ToolDefinitionError,
         | 
| 484 | 
            -
                          "Cannot redefine tool #{display_name.inspect} in #{source.source_name}" \
         | 
| 485 | 
            -
                          " (already defined in #{source_info.source_name})"
         | 
| 486 | 
            -
                  end
         | 
| 487 | 
            -
                  @source_info = source
         | 
| 471 | 
            +
                  @source_info ||= source
         | 
| 488 472 | 
             
                  self
         | 
| 489 473 | 
             
                end
         | 
| 490 474 |  | 
| @@ -1073,7 +1057,7 @@ module Toys | |
| 1073 1057 | 
             
                  unless @definition_finished
         | 
| 1074 1058 | 
             
                    ContextualError.capture("Error installing tool middleware!", tool_name: full_name) do
         | 
| 1075 1059 | 
             
                      config_proc = proc {}
         | 
| 1076 | 
            -
                       | 
| 1060 | 
            +
                      @built_middleware.reverse_each do |middleware|
         | 
| 1077 1061 | 
             
                        config_proc = make_config_proc(middleware, loader, config_proc)
         | 
| 1078 1062 | 
             
                      end
         | 
| 1079 1063 | 
             
                      config_proc.call
         | 
| @@ -1306,7 +1290,11 @@ module Toys | |
| 1306 1290 | 
             
                private
         | 
| 1307 1291 |  | 
| 1308 1292 | 
             
                def make_config_proc(middleware, loader, next_config)
         | 
| 1309 | 
            -
                   | 
| 1293 | 
            +
                  if middleware.respond_to?(:config)
         | 
| 1294 | 
            +
                    proc { middleware.config(self, loader, &next_config) }
         | 
| 1295 | 
            +
                  else
         | 
| 1296 | 
            +
                    next_config
         | 
| 1297 | 
            +
                  end
         | 
| 1310 1298 | 
             
                end
         | 
| 1311 1299 |  | 
| 1312 1300 | 
             
                def make_delegation_run_handler(target)
         | 
| @@ -1,26 +1,5 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            # Copyright 2019 Daniel Azuma
         | 
| 4 | 
            -
            #
         | 
| 5 | 
            -
            # Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            -
            # of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            -
            # in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            -
            # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            -
            # copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            -
            # furnished to do so, subject to the following conditions:
         | 
| 11 | 
            -
            #
         | 
| 12 | 
            -
            # The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            -
            # all copies or substantial portions of the Software.
         | 
| 14 | 
            -
            #
         | 
| 15 | 
            -
            # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            -
            # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            -
            # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            -
            # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            -
            # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
         | 
| 20 | 
            -
            # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
         | 
| 21 | 
            -
            # IN THE SOFTWARE.
         | 
| 22 | 
            -
            ;
         | 
| 23 | 
            -
             | 
| 24 3 | 
             
            require "shellwords"
         | 
| 25 4 |  | 
| 26 5 | 
             
            module Toys
         | 
    
        data/lib/toys/utils/exec.rb
    CHANGED
    
    | @@ -1,26 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
            #
         | 
| 5 | 
            -
            # Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            -
            # of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            -
            # in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            -
            # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            -
            # copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            -
            # furnished to do so, subject to the following conditions:
         | 
| 11 | 
            -
            #
         | 
| 12 | 
            -
            # The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            -
            # all copies or substantial portions of the Software.
         | 
| 14 | 
            -
            #
         | 
| 15 | 
            -
            # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            -
            # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            -
            # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            -
            # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            -
            # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
         | 
| 20 | 
            -
            # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
         | 
| 21 | 
            -
            # IN THE SOFTWARE.
         | 
| 22 | 
            -
            ;
         | 
| 23 | 
            -
             | 
| 3 | 
            +
            require "rbconfig"
         | 
| 24 4 | 
             
            require "logger"
         | 
| 25 5 | 
             
            require "shellwords"
         | 
| 26 6 |  | 
    
        data/lib/toys/utils/gems.rb
    CHANGED
    
    | @@ -1,30 +1,12 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
            # Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            -
            # of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            -
            # in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            -
            # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            -
            # copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            -
            # furnished to do so, subject to the following conditions:
         | 
| 11 | 
            -
            #
         | 
| 12 | 
            -
            # The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            -
            # all copies or substantial portions of the Software.
         | 
| 14 | 
            -
            #
         | 
| 15 | 
            -
            # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            -
            # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            -
            # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            -
            # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            -
            # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
         | 
| 20 | 
            -
            # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
         | 
| 21 | 
            -
            # IN THE SOFTWARE.
         | 
| 22 | 
            -
            ;
         | 
| 3 | 
            +
            require "monitor"
         | 
| 4 | 
            +
            require "rubygems"
         | 
| 23 5 |  | 
| 24 6 | 
             
            module Toys
         | 
| 25 7 | 
             
              module Utils
         | 
| 26 8 | 
             
                ##
         | 
| 27 | 
            -
                # A helper  | 
| 9 | 
            +
                # A helper class that activates and installs gems and sets up bundler.
         | 
| 28 10 | 
             
                #
         | 
| 29 11 | 
             
                # This class is not loaded by default. Before using it directly, you should
         | 
| 30 12 | 
             
                # `require "toys/utils/gems"`
         | 
| @@ -58,6 +40,30 @@ module Toys | |
| 58 40 | 
             
                    end
         | 
| 59 41 | 
             
                  end
         | 
| 60 42 |  | 
| 43 | 
            +
                  ##
         | 
| 44 | 
            +
                  # Failed to run Bundler
         | 
| 45 | 
            +
                  #
         | 
| 46 | 
            +
                  class BundlerFailedError < ::StandardError
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  ##
         | 
| 50 | 
            +
                  # Could not find a Gemfile
         | 
| 51 | 
            +
                  #
         | 
| 52 | 
            +
                  class GemfileNotFoundError < BundlerFailedError
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  ##
         | 
| 56 | 
            +
                  # The bundle is not and could not be installed
         | 
| 57 | 
            +
                  #
         | 
| 58 | 
            +
                  class BundleNotInstalledError < BundlerFailedError
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  ##
         | 
| 62 | 
            +
                  # Bundler has already been run; cannot do so again
         | 
| 63 | 
            +
                  #
         | 
| 64 | 
            +
                  class AlreadyBundledError < BundlerFailedError
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 61 67 | 
             
                  ##
         | 
| 62 68 | 
             
                  # Activate the given gem. If it is not present, attempt to install it (or
         | 
| 63 69 | 
             
                  # inform the user to update the bundle).
         | 
| @@ -73,24 +79,42 @@ module Toys | |
| 73 79 | 
             
                  ##
         | 
| 74 80 | 
             
                  # Create a new gem activator.
         | 
| 75 81 | 
             
                  #
         | 
| 76 | 
            -
                  # @param  | 
| 77 | 
            -
                  #  | 
| 78 | 
            -
                  #  | 
| 79 | 
            -
                  # | 
| 80 | 
            -
                  # | 
| 81 | 
            -
                  #  | 
| 82 | 
            -
                  # | 
| 82 | 
            +
                  # @param on_missing [:confirm,:error,:install] What to do if a needed gem
         | 
| 83 | 
            +
                  #     is not installed. Possible values:
         | 
| 84 | 
            +
                  #      *  `:confirm` - prompt the user on whether to install
         | 
| 85 | 
            +
                  #      *  `:error` - raise an exception
         | 
| 86 | 
            +
                  #      *  `:install` - just install the gem
         | 
| 87 | 
            +
                  #     The default is `:confirm`.
         | 
| 88 | 
            +
                  # @param on_conflict [:error,:warn,:ignore] What to do if bundler has
         | 
| 89 | 
            +
                  #     already been run with a different Gemfile. Possible values:
         | 
| 90 | 
            +
                  #      *  `:error` - raise an exception
         | 
| 91 | 
            +
                  #      *  `:ignore` - just silently proceed without bundling again
         | 
| 92 | 
            +
                  #      *  `:warn` - print a warning and proceed without bundling again
         | 
| 93 | 
            +
                  #     The default is `:error`.
         | 
| 94 | 
            +
                  # @param terminal [Toys::Utils::Terminal] Terminal to use (optional)
         | 
| 95 | 
            +
                  # @param input [IO] Input IO (optional, defaults to STDIN)
         | 
| 96 | 
            +
                  # @param output [IO] Output IO (optional, defaults to STDOUT)
         | 
| 97 | 
            +
                  # @param suppress_confirm [Boolean] Deprecated. Use `on_missing` instead.
         | 
| 98 | 
            +
                  # @param default_confirm [Boolean] Deprecated. Use `on_missing` instead.
         | 
| 83 99 | 
             
                  #
         | 
| 84 | 
            -
                  def initialize( | 
| 85 | 
            -
                                  | 
| 86 | 
            -
                                  | 
| 87 | 
            -
                                  | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
                    @ | 
| 92 | 
            -
                    @ | 
| 93 | 
            -
             | 
| 100 | 
            +
                  def initialize(on_missing: nil,
         | 
| 101 | 
            +
                                 on_conflict: nil,
         | 
| 102 | 
            +
                                 terminal: nil,
         | 
| 103 | 
            +
                                 input: nil,
         | 
| 104 | 
            +
                                 output: nil,
         | 
| 105 | 
            +
                                 suppress_confirm: nil,
         | 
| 106 | 
            +
                                 default_confirm: nil)
         | 
| 107 | 
            +
                    @default_confirm = default_confirm || default_confirm.nil? ? true : false
         | 
| 108 | 
            +
                    @on_missing = on_missing ||
         | 
| 109 | 
            +
                                  if suppress_confirm
         | 
| 110 | 
            +
                                    @default_confirm ? :install : :error
         | 
| 111 | 
            +
                                  else
         | 
| 112 | 
            +
                                    :confirm
         | 
| 113 | 
            +
                                  end
         | 
| 114 | 
            +
                    @on_conflict = on_conflict || :error
         | 
| 115 | 
            +
                    @terminal = terminal
         | 
| 116 | 
            +
                    @input = input || ::STDIN
         | 
| 117 | 
            +
                    @output = output || ::STDOUT
         | 
| 94 118 | 
             
                  end
         | 
| 95 119 |  | 
| 96 120 | 
             
                  ##
         | 
| @@ -102,13 +126,56 @@ module Toys | |
| 102 126 | 
             
                  # @return [void]
         | 
| 103 127 | 
             
                  #
         | 
| 104 128 | 
             
                  def activate(name, *requirements)
         | 
| 105 | 
            -
                     | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 129 | 
            +
                    Gems.synchronize do
         | 
| 130 | 
            +
                      begin
         | 
| 131 | 
            +
                        gem(name, *requirements)
         | 
| 132 | 
            +
                      rescue ::Gem::LoadError => e
         | 
| 133 | 
            +
                        handle_activation_error(e, name, requirements)
         | 
| 134 | 
            +
                      end
         | 
| 135 | 
            +
                    end
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  ##
         | 
| 139 | 
            +
                  # Set up the bundle.
         | 
| 140 | 
            +
                  #
         | 
| 141 | 
            +
                  # @param groups [Array<String>] The groups to include in setup
         | 
| 142 | 
            +
                  # @param search_dirs [Array<String>] Directories to search for a Gemfile
         | 
| 143 | 
            +
                  # @return [void]
         | 
| 144 | 
            +
                  #
         | 
| 145 | 
            +
                  def bundle(groups: nil,
         | 
| 146 | 
            +
                             search_dirs: nil)
         | 
| 147 | 
            +
                    Gems.synchronize do
         | 
| 148 | 
            +
                      gemfile_path = find_gemfile(Array(search_dirs))
         | 
| 149 | 
            +
                      activate("bundler", "~> 2.1")
         | 
| 150 | 
            +
                      if configure_gemfile(gemfile_path)
         | 
| 151 | 
            +
                        setup_bundle(gemfile_path, groups || [])
         | 
| 152 | 
            +
                      end
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  @global_mutex = ::Monitor.new
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                  ## @private
         | 
| 159 | 
            +
                  def self.synchronize(&block)
         | 
| 160 | 
            +
                    @global_mutex.synchronize(&block)
         | 
| 108 161 | 
             
                  end
         | 
| 109 162 |  | 
| 110 163 | 
             
                  private
         | 
| 111 164 |  | 
| 165 | 
            +
                  def terminal
         | 
| 166 | 
            +
                    @terminal ||= begin
         | 
| 167 | 
            +
                      require "toys/utils/terminal"
         | 
| 168 | 
            +
                      Utils::Terminal.new(input: @input, output: @output)
         | 
| 169 | 
            +
                    end
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  def exec_util
         | 
| 173 | 
            +
                    @exec_util ||= begin
         | 
| 174 | 
            +
                      require "toys/utils/exec"
         | 
| 175 | 
            +
                      Utils::Exec.new
         | 
| 176 | 
            +
                    end
         | 
| 177 | 
            +
                  end
         | 
| 178 | 
            +
             | 
| 112 179 | 
             
                  def handle_activation_error(error, name, requirements)
         | 
| 113 180 | 
             
                    is_missing_spec =
         | 
| 114 181 | 
             
                      if defined?(::Gem::MissingSpecError)
         | 
| @@ -116,11 +183,11 @@ module Toys | |
| 116 183 | 
             
                      else
         | 
| 117 184 | 
             
                        error.message.include?("Could not find")
         | 
| 118 185 | 
             
                      end
         | 
| 119 | 
            -
                     | 
| 186 | 
            +
                    if !is_missing_spec || @on_missing == :error
         | 
| 120 187 | 
             
                      report_error(name, requirements, error)
         | 
| 121 188 | 
             
                      return
         | 
| 122 189 | 
             
                    end
         | 
| 123 | 
            -
                     | 
| 190 | 
            +
                    confirm_and_install_gem(name, requirements)
         | 
| 124 191 | 
             
                    begin
         | 
| 125 192 | 
             
                      gem(name, *requirements)
         | 
| 126 193 | 
             
                    rescue ::Gem::LoadError => e
         | 
| @@ -132,28 +199,16 @@ module Toys | |
| 132 199 | 
             
                    "#{name.inspect}, #{requirements.map(&:inspect).join(', ')}"
         | 
| 133 200 | 
             
                  end
         | 
| 134 201 |  | 
| 135 | 
            -
                  def  | 
| 136 | 
            -
                     | 
| 137 | 
            -
             | 
| 138 | 
            -
                       | 
| 139 | 
            -
             | 
| 140 | 
            -
                       | 
| 141 | 
            -
                         | 
| 142 | 
            -
                                          default: @default_confirm)
         | 
| 202 | 
            +
                  def confirm_and_install_gem(name, requirements)
         | 
| 203 | 
            +
                    if @on_missing == :confirm
         | 
| 204 | 
            +
                      requirements_text = gem_requirements_text(name, requirements)
         | 
| 205 | 
            +
                      response = terminal.confirm("Gem needed: #{requirements_text}. Install? ",
         | 
| 206 | 
            +
                                                  default: @default_confirm)
         | 
| 207 | 
            +
                      unless response
         | 
| 208 | 
            +
                        raise InstallFailedError, "Canceled installation of needed gem: #{requirements_text}"
         | 
| 143 209 | 
             
                      end
         | 
| 144 | 
            -
                    unless response
         | 
| 145 | 
            -
                      raise InstallFailedError, "Canceled installation of needed gem: #{requirements_text}"
         | 
| 146 | 
            -
                    end
         | 
| 147 | 
            -
                    perform_install(name, requirements)
         | 
| 148 | 
            -
                  end
         | 
| 149 | 
            -
             | 
| 150 | 
            -
                  def perform_install(name, requirements)
         | 
| 151 | 
            -
                    result = @terminal.spinner(leading_text: "Installing gem #{name}... ",
         | 
| 152 | 
            -
                                               final_text: "Done.\n") do
         | 
| 153 | 
            -
                      @exec.exec(["gem", "install", name, "--version", requirements.join(",")],
         | 
| 154 | 
            -
                                 out: :capture, err: :capture)
         | 
| 155 210 | 
             
                    end
         | 
| 156 | 
            -
                     | 
| 211 | 
            +
                    result = exec_util.exec(["gem", "install", name, "--version", requirements.join(",")])
         | 
| 157 212 | 
             
                    if result.error?
         | 
| 158 213 | 
             
                      raise InstallFailedError, "Failed to install gem #{name}"
         | 
| 159 214 | 
             
                    end
         | 
| @@ -167,6 +222,62 @@ module Toys | |
| 167 222 | 
             
                    end
         | 
| 168 223 | 
             
                    raise ActivationFailedError, err.message
         | 
| 169 224 | 
             
                  end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                  def find_gemfile(search_dirs)
         | 
| 227 | 
            +
                    search_dirs.each do |dir|
         | 
| 228 | 
            +
                      gemfile_path = ::File.join(dir, "Gemfile")
         | 
| 229 | 
            +
                      return gemfile_path if ::File.readable?(gemfile_path)
         | 
| 230 | 
            +
                    end
         | 
| 231 | 
            +
                    raise GemfileNotFoundError, "Gemfile not found"
         | 
| 232 | 
            +
                  end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                  def configure_gemfile(gemfile_path)
         | 
| 235 | 
            +
                    old_path = ::ENV["BUNDLE_GEMFILE"]
         | 
| 236 | 
            +
                    if old_path && gemfile_path != old_path
         | 
| 237 | 
            +
                      case @on_conflict
         | 
| 238 | 
            +
                      when :warn
         | 
| 239 | 
            +
                        terminal.puts("Warning: could not set up bundler because it is already set up.", :red)
         | 
| 240 | 
            +
                      when :error
         | 
| 241 | 
            +
                        raise AlreadyBundledError, "Could not set up bundler because it is already set up"
         | 
| 242 | 
            +
                      end
         | 
| 243 | 
            +
                      return false
         | 
| 244 | 
            +
                    end
         | 
| 245 | 
            +
                    ::ENV["BUNDLE_GEMFILE"] = gemfile_path
         | 
| 246 | 
            +
                    true
         | 
| 247 | 
            +
                  end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                  def setup_bundle(gemfile_path, groups)
         | 
| 250 | 
            +
                    require "bundler"
         | 
| 251 | 
            +
                    begin
         | 
| 252 | 
            +
                      ::Bundler.setup(*groups)
         | 
| 253 | 
            +
                    rescue ::Bundler::GemNotFound
         | 
| 254 | 
            +
                      install_bundle(gemfile_path)
         | 
| 255 | 
            +
                      ::Bundler.reset!
         | 
| 256 | 
            +
                      ::Bundler.setup(*groups)
         | 
| 257 | 
            +
                    end
         | 
| 258 | 
            +
                  end
         | 
| 259 | 
            +
             | 
| 260 | 
            +
                  def permission_to_bundle?
         | 
| 261 | 
            +
                    case @on_missing
         | 
| 262 | 
            +
                    when :install
         | 
| 263 | 
            +
                      true
         | 
| 264 | 
            +
                    when :error
         | 
| 265 | 
            +
                      false
         | 
| 266 | 
            +
                    else
         | 
| 267 | 
            +
                      terminal.confirm("Your bundle is not complete. Install? ", default: @default_confirm)
         | 
| 268 | 
            +
                    end
         | 
| 269 | 
            +
                  end
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                  def install_bundle(gemfile_path)
         | 
| 272 | 
            +
                    gemfile_dir = ::File.dirname(gemfile_path)
         | 
| 273 | 
            +
                    unless permission_to_bundle?
         | 
| 274 | 
            +
                      raise BundleNotInstalledError,
         | 
| 275 | 
            +
                            "Your bundle is not installed. Consider running" \
         | 
| 276 | 
            +
                              " `cd #{gemfile_dir} && bundle install`"
         | 
| 277 | 
            +
                    end
         | 
| 278 | 
            +
                    require "bundler/cli"
         | 
| 279 | 
            +
                    ::Bundler::CLI.start(["install"])
         | 
| 280 | 
            +
                  end
         | 
| 170 281 | 
             
                end
         | 
| 171 282 | 
             
              end
         | 
| 172 283 | 
             
            end
         |