psychic-runner 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +29 -0
- data/.travis.yml +30 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +70 -0
- data/Rakefile +12 -0
- data/bin/psychic +4 -0
- data/lib/psychic/cli.rb +37 -0
- data/lib/psychic/commands/exec.rb +19 -0
- data/lib/psychic/logger.rb +17 -0
- data/lib/psychic/runner/base_runner.rb +68 -0
- data/lib/psychic/runner/cold/shell_script_runner.rb +38 -0
- data/lib/psychic/runner/cold_runner_registry.rb +31 -0
- data/lib/psychic/runner/hot_runner.rb +25 -0
- data/lib/psychic/runner/sample_runner.rb +50 -0
- data/lib/psychic/runner/version.rb +5 -0
- data/lib/psychic/runner.rb +37 -0
- data/lib/psychic/shell/execution_result.rb +42 -0
- data/lib/psychic/shell/mixlib_shellout_executor.rb +62 -0
- data/lib/psychic/shell.rb +19 -0
- data/lib/psychic/tokens.rb +39 -0
- data/lib/psychic/util.rb +108 -0
- data/psychic-runner.gemspec +33 -0
- data/spec/psychic/runner/cold/shell_script_runner_spec.rb +80 -0
- data/spec/psychic/runner/hot_runner_spec.rb +63 -0
- data/spec/psychic/runner_spec.rb +45 -0
- data/spec/spec_helper.rb +47 -0
- metadata +239 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 67df9a60685ee7062b1b4a26ef84a74157338023
         | 
| 4 | 
            +
              data.tar.gz: 5fd8577946b451d79ac3b7b928538089d66f512e
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 8ef2abd35ba79d48c86b27389731b6b6962dcd793a4810b144b439d1b846351f7052cc213a4d77463c468f7dc22ba825180e723244e45eab4001026c5b0f8a62
         | 
| 7 | 
            +
              data.tar.gz: f254488c0fa32cd13e8f8c7477988d3d61b7a5b165e08d31626f07834d0341fd33097ac85eeb101ae73d3fbeb1e7b9d5a8aea864065b620eb8bc02ab6afd9a5e
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    
    
        data/.rubocop.yml
    ADDED
    
    
    
        data/.rubocop_todo.yml
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # This configuration was generated by `rubocop --auto-gen-config`
         | 
| 2 | 
            +
            # on 2014-11-20 15:08:06 -0500 using RuboCop version 0.27.1.
         | 
| 3 | 
            +
            # The point is for the user to remove these configuration records
         | 
| 4 | 
            +
            # one by one as the offenses are removed from the code base.
         | 
| 5 | 
            +
            # Note that changes in the inspected code, or installation of new
         | 
| 6 | 
            +
            # versions of RuboCop, may require this file to be generated again.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            # Offense count: 7
         | 
| 9 | 
            +
            # Configuration parameters: AllowURI, URISchemes.
         | 
| 10 | 
            +
            Metrics/LineLength:
         | 
| 11 | 
            +
              Max: 104
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            # Offense count: 1
         | 
| 14 | 
            +
            # Configuration parameters: CountComments.
         | 
| 15 | 
            +
            Metrics/MethodLength:
         | 
| 16 | 
            +
              Max: 11
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            # Offense count: 14
         | 
| 19 | 
            +
            Style/Documentation:
         | 
| 20 | 
            +
              Enabled: false
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            # Offense count: 1
         | 
| 23 | 
            +
            # Configuration parameters: MinBodyLength.
         | 
| 24 | 
            +
            Style/GuardClause:
         | 
| 25 | 
            +
              Enabled: false
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            # Offense count: 2
         | 
| 28 | 
            +
            Style/RegexpLiteral:
         | 
| 29 | 
            +
              MaxSlashes: 0
         | 
    
        data/.travis.yml
    ADDED
    
    | @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            language: php
         | 
| 2 | 
            +
            php:
         | 
| 3 | 
            +
              - "5.6"
         | 
| 4 | 
            +
              - "5.5"
         | 
| 5 | 
            +
              - "5.4"
         | 
| 6 | 
            +
              - "5.3"
         | 
| 7 | 
            +
              - hhvm
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            matrix:
         | 
| 10 | 
            +
              allow_failures:
         | 
| 11 | 
            +
                - php: hhvm
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            branches:
         | 
| 14 | 
            +
              only:
         | 
| 15 | 
            +
                - master
         | 
| 16 | 
            +
                - working
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            before_script:
         | 
| 19 | 
            +
              - composer install --prefer-source
         | 
| 20 | 
            +
              - vendor/bin/parallel-lint --exclude vendor .
         | 
| 21 | 
            +
              - vendor/bin/php-cs-fixer fix --dry-run --level psr2 .
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            after_script:
         | 
| 24 | 
            +
              - php vendor/bin/coveralls -v
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            notifications:
         | 
| 27 | 
            +
              email:
         | 
| 28 | 
            +
                - jamie.hannaford@rackspace.com
         | 
| 29 | 
            +
                - glen.campbell@rackspace.com
         | 
| 30 | 
            +
                - shaunak.kashyap@rackspace.com
         | 
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2014 Max Lincoln
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            MIT License
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 6 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 7 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 8 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 9 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 10 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 11 | 
            +
            the following conditions:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 14 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 17 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 18 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 19 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 20 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 21 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 22 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,70 @@ | |
| 1 | 
            +
            **php-opencloud**
         | 
| 2 | 
            +
            =============
         | 
| 3 | 
            +
            PHP SDK for OpenStack/Rackspace APIs
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            [](https://packagist.org/packages/rackspace/php-opencloud) [](https://travis-ci.org/rackspace/php-opencloud) [](https://packagist.org/packages/rackspace/php-opencloud)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            For SDKs in different languages, see http://developer.rackspace.com.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            The PHP SDK should work with most OpenStack-based cloud deployments,
         | 
| 10 | 
            +
            though it specifically targets the Rackspace public cloud. In
         | 
| 11 | 
            +
            general, whenever a Rackspace deployment is substantially different
         | 
| 12 | 
            +
            than a pure OpenStack one, a separate Rackspace subclass is provided
         | 
| 13 | 
            +
            so that you can still use the SDK with a pure OpenStack instance
         | 
| 14 | 
            +
            (for example, see the `OpenStack` class (for OpenStack) and the
         | 
| 15 | 
            +
            `Rackspace` subclass).
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Requirements
         | 
| 18 | 
            +
            ------------
         | 
| 19 | 
            +
            * PHP >=5.3.3
         | 
| 20 | 
            +
            * cURL extension for PHP
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            Installation
         | 
| 23 | 
            +
            ------------
         | 
| 24 | 
            +
            You must install this library through Composer:
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            ```bash
         | 
| 27 | 
            +
            # Install Composer
         | 
| 28 | 
            +
            curl -sS https://getcomposer.org/installer | php
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            # Require php-opencloud as a dependency
         | 
| 31 | 
            +
            php composer.phar require rackspace/php-opencloud
         | 
| 32 | 
            +
            ```
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            Once you have installed the library, you will need to load Composer's autoloader (which registers all the required
         | 
| 35 | 
            +
            namespaces):
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            ```php
         | 
| 38 | 
            +
            require 'vendor/autoload.php';
         | 
| 39 | 
            +
            ```
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            And you're ready to go!
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            You can also check out the [Getting Started guide](docs/getting-started.md) for a quick tutorial.
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            - - -
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            Alternatively, if you would like to fork or clone the repository into a directory (to work and submit pull requests),
         | 
| 48 | 
            +
            you will need to execute:
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            ```bash
         | 
| 51 | 
            +
            php composer.phar install
         | 
| 52 | 
            +
            ```
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            Instead of the `require` command. You can also specify the `--no-dev` option if you do not want to install phpDocumentor
         | 
| 55 | 
            +
            (which has lots of vendor folders).
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            Support and Feedback
         | 
| 58 | 
            +
            --------------------
         | 
| 59 | 
            +
            Your feedback is appreciated! If you have specific problems or bugs with this SDK, please file an issue on Github. We
         | 
| 60 | 
            +
            also have a [mailing list](https://groups.google.com/forum/#!forum/php-opencloud), so feel free to join to keep up to
         | 
| 61 | 
            +
            date with all the latest changes and announcements to the library.
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            For general feedback and support requests, send an email to sdk-support@rackspace.com.
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            You can also find assistance via IRC on #rackspace at freenode.net.
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            Contributing
         | 
| 68 | 
            +
            ------------
         | 
| 69 | 
            +
            If you'd like to contribute to the project, or require help running the unit/acceptance tests, please view the
         | 
| 70 | 
            +
            [contributing guidelines](https://github.com/rackspace/php-opencloud/blob/master/CONTRIBUTING.md).
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            require 'bundler/gem_tasks'
         | 
| 2 | 
            +
            require 'rubocop/rake_task'
         | 
| 3 | 
            +
            require 'rake/notes/rake_task'
         | 
| 4 | 
            +
            require 'rspec/core/rake_task'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            task default: [:spec, :rubocop, :notes]
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            RSpec::Core::RakeTask.new('spec')
         | 
| 9 | 
            +
            RuboCop::RakeTask.new(:rubocop) do |task|
         | 
| 10 | 
            +
              # abort rake on failure
         | 
| 11 | 
            +
              task.fail_on_error = true
         | 
| 12 | 
            +
            end
         | 
    
        data/bin/psychic
    ADDED
    
    
    
        data/lib/psychic/cli.rb
    ADDED
    
    | @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            require 'thor'
         | 
| 2 | 
            +
            require 'psychic/runner'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Psychic
         | 
| 5 | 
            +
              class CLI < Thor
         | 
| 6 | 
            +
                desc 'run_task <name>', 'Executes a custom task by name'
         | 
| 7 | 
            +
                def run_task(task_name, *args)
         | 
| 8 | 
            +
                  result = runner.execute_task(task_name, *args)
         | 
| 9 | 
            +
                  result.error!
         | 
| 10 | 
            +
                  say_status :success, task_name
         | 
| 11 | 
            +
                rescue Psychic::Shell::ExecutionError => e
         | 
| 12 | 
            +
                  say_status :failed, task_name, :red
         | 
| 13 | 
            +
                  say e.execution_result if e.execution_result
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                desc 'run_sample <name>', 'Executes a code sample'
         | 
| 17 | 
            +
                def run_sample(sample_name, *args)
         | 
| 18 | 
            +
                  result = runner.run_sample(sample_name, *args)
         | 
| 19 | 
            +
                  result.error!
         | 
| 20 | 
            +
                  say_status :success, sample_name
         | 
| 21 | 
            +
                rescue Errno::ENOENT => e
         | 
| 22 | 
            +
                  say_status :failed, "No code sample found for #{sample_name}", :red
         | 
| 23 | 
            +
                rescue Psychic::Shell::ExecutionError => e
         | 
| 24 | 
            +
                  say_status :failed, "Executing sample #{sample_name}", :red
         | 
| 25 | 
            +
                  say e.execution_result if e.execution_result
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def runner
         | 
| 31 | 
            +
                  # Psychic::Shell.shell = shell
         | 
| 32 | 
            +
                  @runner ||= Psychic::Runner.new
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            # require 'psychic/commands/exec'
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              module Commands
         | 
| 3 | 
            +
                class Exec < Thor
         | 
| 4 | 
            +
                  desc 'task <name>', 'Executes a custom task by name'
         | 
| 5 | 
            +
                  def task(task_name)
         | 
| 6 | 
            +
                    # Psychic::Shell.shell = shell
         | 
| 7 | 
            +
                    runner = Psychic::Runner.new
         | 
| 8 | 
            +
                    result = runner.public_send(task_name.to_sym)
         | 
| 9 | 
            +
                    result.error!
         | 
| 10 | 
            +
                    say_status :success, task_name
         | 
| 11 | 
            +
                  rescue Psychic::Shell::ExecutionError => e
         | 
| 12 | 
            +
                    say_status :failed, task_name, :red
         | 
| 13 | 
            +
                    say e.execution_result if e.execution_result
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            Psychic::CLI.register(Psychic::Commands::Exec, 'exec', 'exec <task>', 'Execute things via psychic')
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            require 'logger'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Psychic
         | 
| 4 | 
            +
              module Logger
         | 
| 5 | 
            +
                def logger
         | 
| 6 | 
            +
                  @logger ||= new_logger
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def new_logger(_io = $stdout, _level = :debug)
         | 
| 10 | 
            +
                  ::Logger.new(STDOUT)
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def log_level=(level)
         | 
| 14 | 
            +
                  logger.level = level
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,68 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              class Runner
         | 
| 3 | 
            +
                module BaseRunner
         | 
| 4 | 
            +
                  include Psychic::Shell
         | 
| 5 | 
            +
                  include Psychic::Logger
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  attr_reader :known_tasks
         | 
| 8 | 
            +
                  attr_reader :cwd
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  module ClassMethods
         | 
| 11 | 
            +
                    attr_accessor :magic_file_pattern
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def register_runner
         | 
| 14 | 
            +
                      Psychic::Runner::ColdRunnerRegistry.register(self)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def magic_file(pattern) # rubocop:disable Style/TrivialAccessors
         | 
| 18 | 
            +
                      @magic_file_pattern = pattern
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def self.included(base)
         | 
| 23 | 
            +
                    base.extend(ClassMethods)
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def initialize(opts = {})
         | 
| 27 | 
            +
                    opts[:cwd] ||= Dir.pwd
         | 
| 28 | 
            +
                    @logger = opts[:logger] || new_logger
         | 
| 29 | 
            +
                    @cwd = opts[:cwd]
         | 
| 30 | 
            +
                    @opts = opts
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def respond_to_missing?(task, include_all = false)
         | 
| 34 | 
            +
                    return true if known_tasks.include?(task.to_s)
         | 
| 35 | 
            +
                    super
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def method_missing(task, *args, &block)
         | 
| 39 | 
            +
                    execute_task(task, *args)
         | 
| 40 | 
            +
                  rescue Psychic::Runner::TaskNotImplementedError
         | 
| 41 | 
            +
                    super
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  # Reserved words
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def execute(command, *args)
         | 
| 47 | 
            +
                    full_cmd = [command, *args].join(' ')
         | 
| 48 | 
            +
                    logger.info("Executing #{full_cmd}")
         | 
| 49 | 
            +
                    shell.execute(full_cmd, @opts)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def command_for_task(task, *_args)
         | 
| 53 | 
            +
                    task_name = task.to_s
         | 
| 54 | 
            +
                    self[task_name]
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def execute_task(task, *args)
         | 
| 58 | 
            +
                    command = command_for_task(task, *args)
         | 
| 59 | 
            +
                    fail Psychic::Runner::TaskNotImplementedError if command.nil?
         | 
| 60 | 
            +
                    execute(command, *args)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def active?
         | 
| 64 | 
            +
                    self.class.magic_file_pattern ? false : Dir["#{@cwd}/#{self.class.magic_file_pattern}"]
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              class Runner
         | 
| 3 | 
            +
                module Cold
         | 
| 4 | 
            +
                  class ShellScriptRunner
         | 
| 5 | 
            +
                    include BaseRunner
         | 
| 6 | 
            +
                    EXTENSIONS = ['.sh', '']
         | 
| 7 | 
            +
                    magic_file 'scripts/*'
         | 
| 8 | 
            +
                    register_runner
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    def initialize(opts)
         | 
| 11 | 
            +
                      super
         | 
| 12 | 
            +
                      @known_tasks = Dir["#{@cwd}/scripts/*"].map do | script |
         | 
| 13 | 
            +
                        File.basename(script, File.extname(script)) if EXTENSIONS.include?(File.extname(script))
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def [](task_name)
         | 
| 18 | 
            +
                      task = task_name.to_s
         | 
| 19 | 
            +
                      script = Dir["#{@cwd}/scripts/#{task}{.sh,}"].first
         | 
| 20 | 
            +
                      if script
         | 
| 21 | 
            +
                        cmd = Psychic::Util.relativize(script, @cwd)
         | 
| 22 | 
            +
                        cmd = [cmd, args_for_task(task_name)].compact.join(' ')
         | 
| 23 | 
            +
                        "./#{cmd}" unless cmd.to_s.start_with? '/'
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    def args_for_task(task)
         | 
| 28 | 
            +
                      # HACK: Need a better way to deal with args
         | 
| 29 | 
            +
                      '{{sample_file}}' if task == 'run_sample'
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    def active?
         | 
| 33 | 
            +
                      true
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              class Runner
         | 
| 3 | 
            +
                class ColdRunnerRegistry
         | 
| 4 | 
            +
                  include Psychic::Logger
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  BUILT_IN_DIR = File.expand_path('../cold', __FILE__)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  class << self
         | 
| 9 | 
            +
                    def autoload_runners!
         | 
| 10 | 
            +
                      # Load built-in runners
         | 
| 11 | 
            +
                      Dir["#{BUILT_IN_DIR}/*.rb"].each do |cold_runner_file|
         | 
| 12 | 
            +
                        require cold_runner_file
         | 
| 13 | 
            +
                      end
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    def runner_classes
         | 
| 17 | 
            +
                      @runner_classes ||= Set.new
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def register(klass)
         | 
| 21 | 
            +
                      runner_classes.add klass
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def active_runners(opts)
         | 
| 25 | 
            +
                      runners = runner_classes.map { |k| k.new(opts) }
         | 
| 26 | 
            +
                      runners.select(&:active?)
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              class Runner
         | 
| 3 | 
            +
                class HotRunner
         | 
| 4 | 
            +
                  include BaseRunner
         | 
| 5 | 
            +
                  def initialize(opts = {})
         | 
| 6 | 
            +
                    hints = opts.delete :hints
         | 
| 7 | 
            +
                    super
         | 
| 8 | 
            +
                    @hints = Psychic::Util.stringified_hash(hints || load_hints || {})
         | 
| 9 | 
            +
                    @tasks = @hints['tasks'] || {}
         | 
| 10 | 
            +
                    @known_tasks = @tasks.keys
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def [](task_name)
         | 
| 14 | 
            +
                    @tasks[task_name]
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  private
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def load_hints
         | 
| 20 | 
            +
                    hints_file = Dir["#{@cwd}/psychic-hints.{yaml,yml}"].first
         | 
| 21 | 
            +
                    YAML.load(File.read(hints_file)) unless hints_file.nil?
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              class Runner
         | 
| 3 | 
            +
                module SampleRunner
         | 
| 4 | 
            +
                  def run_sample(code_sample, *args)
         | 
| 5 | 
            +
                    sample_file = Psychic::Util.find_file_by_alias(code_sample, cwd)
         | 
| 6 | 
            +
                    process_template(sample_file) if templated?
         | 
| 7 | 
            +
                    command = command_for_task('run_sample')
         | 
| 8 | 
            +
                    if command
         | 
| 9 | 
            +
                      variables = { sample: code_sample, sample_file: sample_file }
         | 
| 10 | 
            +
                      command = Psychic::Util.replace_tokens(command, variables)
         | 
| 11 | 
            +
                      execute(command, *args)
         | 
| 12 | 
            +
                    else
         | 
| 13 | 
            +
                      run_sample_file(sample_file)
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def run_sample_file(sample_file, *args)
         | 
| 18 | 
            +
                    execute("./#{sample_file}", *args) # Assuming Bash, but should detect Windows and use PowerShell
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def process_template(sample_file)
         | 
| 22 | 
            +
                    absolute_sample_file = File.expand_path(sample_file, cwd)
         | 
| 23 | 
            +
                    template = File.read(absolute_sample_file)
         | 
| 24 | 
            +
                    # Default token pattern/replacement (used by php-opencloud) should be configurable
         | 
| 25 | 
            +
                    content = Psychic::Util.replace_tokens(template, variables, /'\{(\w+)\}'/, "'\\1'")
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    # Backup and overwrite
         | 
| 28 | 
            +
                    backup_file = "#{absolute_sample_file}.bak"
         | 
| 29 | 
            +
                    fail 'Please clear out old backups before rerunning' if File.exist? backup_file
         | 
| 30 | 
            +
                    FileUtils.cp(absolute_sample_file, backup_file)
         | 
| 31 | 
            +
                    File.write(absolute_sample_file, content)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def templated?
         | 
| 35 | 
            +
                    # Probably not the best way to turn this on/off
         | 
| 36 | 
            +
                    true unless variables.nil?
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def variables
         | 
| 40 | 
            +
                    # ... or
         | 
| 41 | 
            +
                    variables_file = Dir["#{cwd}/psychic-variables.{yaml,yml}"].first
         | 
| 42 | 
            +
                    return nil unless variables_file
         | 
| 43 | 
            +
                    environment_variables = ENV.to_hash
         | 
| 44 | 
            +
                    environment_variables.merge!(@opts[:env]) if @opts[:env]
         | 
| 45 | 
            +
                    variables = Psychic::Util.replace_tokens(File.read(variables_file), environment_variables)
         | 
| 46 | 
            +
                    YAML.load(variables)
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            require 'psychic/runner/version'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            autoload :YAML, 'yaml'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Psychic
         | 
| 6 | 
            +
              autoload :Util,   'psychic/util'
         | 
| 7 | 
            +
              autoload :Logger, 'psychic/logger'
         | 
| 8 | 
            +
              autoload :Shell,  'psychic/shell'
         | 
| 9 | 
            +
              class Runner
         | 
| 10 | 
            +
                autoload :BaseRunner, 'psychic/runner/base_runner'
         | 
| 11 | 
            +
                autoload :SampleRunner, 'psychic/runner/sample_runner'
         | 
| 12 | 
            +
                autoload :HotRunner, 'psychic/runner/hot_runner'
         | 
| 13 | 
            +
                autoload :CompoundRunner, 'psychic/runner/compound_runner'
         | 
| 14 | 
            +
                autoload :ColdRunnerRegistry, 'psychic/runner/cold_runner_registry'
         | 
| 15 | 
            +
                class TaskNotImplementedError < NotImplementedError; end
         | 
| 16 | 
            +
                ColdRunnerRegistry.autoload_runners!
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                include BaseRunner
         | 
| 19 | 
            +
                include SampleRunner
         | 
| 20 | 
            +
                attr_reader :runners, :hot_runner, :cold_runners
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def initialize(opts = { cwd: Dir.pwd })
         | 
| 23 | 
            +
                  fail 'cwd is required' unless opts[:cwd]
         | 
| 24 | 
            +
                  super
         | 
| 25 | 
            +
                  @hot_runner = HotRunner.new(opts)
         | 
| 26 | 
            +
                  @cold_runners = ColdRunnerRegistry.active_runners(opts)
         | 
| 27 | 
            +
                  @runners = [@hot_runner, @cold_runners].flatten
         | 
| 28 | 
            +
                  @known_tasks = @runners.map(&:known_tasks).uniq
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def [](task_name)
         | 
| 32 | 
            +
                  runner = runners.find { |r| r.command_for_task(task_name) }
         | 
| 33 | 
            +
                  return nil unless runner
         | 
| 34 | 
            +
                  runner[task_name]
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            require 'English'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Psychic
         | 
| 4 | 
            +
              module Shell
         | 
| 5 | 
            +
                class ExecutionError < StandardError
         | 
| 6 | 
            +
                  attr_accessor :execution_result
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                class ExecutionResult
         | 
| 10 | 
            +
                  attr_reader :exitstatus
         | 
| 11 | 
            +
                  attr_reader :stdout
         | 
| 12 | 
            +
                  attr_reader :stderr
         | 
| 13 | 
            +
                  # coerce_value String, ->(v) { v.force_encoding('utf-8') }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def initialize(results)
         | 
| 16 | 
            +
                    @exitstatus = results.fetch(:exitstatus)
         | 
| 17 | 
            +
                    # Needs to be UTF-8 to serialize as YAML
         | 
| 18 | 
            +
                    @stdout = results.fetch(:stdout).force_encoding('utf-8')
         | 
| 19 | 
            +
                    @stderr = results.fetch(:stderr).force_encoding('utf-8')
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def error!
         | 
| 23 | 
            +
                    if @exitstatus != 0
         | 
| 24 | 
            +
                      error = ExecutionError.new
         | 
| 25 | 
            +
                      error.execution_result = self
         | 
| 26 | 
            +
                      fail error
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def to_s
         | 
| 31 | 
            +
                    ''"
         | 
| 32 | 
            +
                    Execution Result:
         | 
| 33 | 
            +
                      exitstatus: #{exitstatus}
         | 
| 34 | 
            +
                      stdout:
         | 
| 35 | 
            +
                    #{stdout}
         | 
| 36 | 
            +
                      stderr:
         | 
| 37 | 
            +
                    #{stderr}
         | 
| 38 | 
            +
                    "''
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            require 'mixlib/shellout'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Psychic
         | 
| 4 | 
            +
              module Shell
         | 
| 5 | 
            +
                class IOToLog < IO
         | 
| 6 | 
            +
                  def initialize(logger)
         | 
| 7 | 
            +
                    @logger = logger
         | 
| 8 | 
            +
                    @buffer = ''
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def write(string)
         | 
| 12 | 
            +
                    (@buffer + string).lines.each do |line|
         | 
| 13 | 
            +
                      if line.end_with? "\n"
         | 
| 14 | 
            +
                        @buffer = ''
         | 
| 15 | 
            +
                        @logger.info(line.rstrip)
         | 
| 16 | 
            +
                      else
         | 
| 17 | 
            +
                        @buffer = line
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                class MixlibShellOutExecutor
         | 
| 24 | 
            +
                  include Psychic::Logger
         | 
| 25 | 
            +
                  attr_reader :shell
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  MIXLIB_SHELLOUT_EXCEPTION_CLASSES = Mixlib::ShellOut.constants.map do|name|
         | 
| 28 | 
            +
                    klass = Mixlib::ShellOut.const_get(name)
         | 
| 29 | 
            +
                    if klass.is_a?(Class) && klass <= RuntimeError
         | 
| 30 | 
            +
                      klass
         | 
| 31 | 
            +
                    else
         | 
| 32 | 
            +
                      nil
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end.compact
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def execute(command, opts)
         | 
| 37 | 
            +
                    @logger = opts.delete(:logger) || logger
         | 
| 38 | 
            +
                    @shell = Mixlib::ShellOut.new(command, opts)
         | 
| 39 | 
            +
                    @shell.live_stream = IOToLog.new(@logger)
         | 
| 40 | 
            +
                    @shell.run_command
         | 
| 41 | 
            +
                    execution_result
         | 
| 42 | 
            +
                  rescue SystemCallError, *MIXLIB_SHELLOUT_EXCEPTION_CLASSES, TypeError => e
         | 
| 43 | 
            +
                    # See https://github.com/opscode/mixlib-shellout/issues/62
         | 
| 44 | 
            +
                    execution_error = ExecutionError.new(e)
         | 
| 45 | 
            +
                    execution_error.execution_result = execution_result
         | 
| 46 | 
            +
                    raise execution_error
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  private
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def execution_result
         | 
| 52 | 
            +
                    return nil if shell.nil?
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    ExecutionResult.new(
         | 
| 55 | 
            +
                      exitstatus: shell.exitstatus,
         | 
| 56 | 
            +
                      stdout: shell.stdout,
         | 
| 57 | 
            +
                      stderr: shell.stderr
         | 
| 58 | 
            +
                    )
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              module Shell
         | 
| 3 | 
            +
                autoload :ExecutionResult, 'psychic/shell/execution_result'
         | 
| 4 | 
            +
                autoload :ExecutionError, 'psychic/shell/execution_result'
         | 
| 5 | 
            +
                autoload :MixlibShellOutExecutor, 'psychic/shell/mixlib_shellout_executor'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                class << self
         | 
| 8 | 
            +
                  attr_writer :shell
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def self.shell
         | 
| 12 | 
            +
                  @shell ||=  MixlibShellOutExecutor.new
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def shell
         | 
| 16 | 
            +
                  Psychic::Shell.shell
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            autoload :Mustache, 'mustache'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Psychic
         | 
| 4 | 
            +
              class RegexpTokenHandler
         | 
| 5 | 
            +
                def initialize(template, token_pattern, token_replacement)
         | 
| 6 | 
            +
                  @template = template
         | 
| 7 | 
            +
                  @token_pattern = token_pattern
         | 
| 8 | 
            +
                  @token_replacement = token_replacement
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def tokens
         | 
| 12 | 
            +
                  @template.scan(@token_pattern).flatten.uniq
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def replace(variables = {})
         | 
| 16 | 
            +
                  @template.gsub(@token_pattern) do
         | 
| 17 | 
            +
                    full_match = Regexp.last_match[0]
         | 
| 18 | 
            +
                    key = Regexp.last_match[1]
         | 
| 19 | 
            +
                    value = variables[key]
         | 
| 20 | 
            +
                    value = @token_replacement.gsub('\\1', value.to_s) unless @token_replacement.nil?
         | 
| 21 | 
            +
                    full_match.gsub(@token_pattern, value)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              class MustacheTokenHandler
         | 
| 27 | 
            +
                def initialize(template)
         | 
| 28 | 
            +
                  @template = Mustache::Template.new(template)
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def tokens
         | 
| 32 | 
            +
                  @template.tags
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def replace(variables = {})
         | 
| 36 | 
            +
                  Mustache.render(@template, variables)
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
    
        data/lib/psychic/util.rb
    ADDED
    
    | @@ -0,0 +1,108 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              autoload :RegexpTokenHandler, 'psychic/tokens'
         | 
| 3 | 
            +
              autoload :MustacheTokenHandler, 'psychic/tokens'
         | 
| 4 | 
            +
              class Util
         | 
| 5 | 
            +
                # Returns a new Hash with all key values coerced to strings. All keys
         | 
| 6 | 
            +
                # within a Hash are coerced by calling #to_s and hashes with arrays
         | 
| 7 | 
            +
                # and other hashes are traversed.
         | 
| 8 | 
            +
                #
         | 
| 9 | 
            +
                # @param obj [Object] the hash to be processed. While intended for
         | 
| 10 | 
            +
                #   hashes, this method safely processes arbitrary objects
         | 
| 11 | 
            +
                # @return [Object] a converted hash with all keys as strings
         | 
| 12 | 
            +
                def self.stringified_hash(obj)
         | 
| 13 | 
            +
                  if obj.is_a?(Hash)
         | 
| 14 | 
            +
                    obj.each_with_object({}) do |(k, v), h|
         | 
| 15 | 
            +
                      h[k.to_s] = stringified_hash(v)
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  elsif obj.is_a?(Array)
         | 
| 18 | 
            +
                    obj.each_with_object([]) do |e, a|
         | 
| 19 | 
            +
                      a << stringified_hash(e)
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                  else
         | 
| 22 | 
            +
                    obj
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def self.relativize(file, base_path)
         | 
| 27 | 
            +
                  absolute_file = File.absolute_path(file)
         | 
| 28 | 
            +
                  absolute_base_path = File.absolute_path(base_path)
         | 
| 29 | 
            +
                  Pathname.new(absolute_file).relative_path_from Pathname.new(absolute_base_path)
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def self.slugify(*labels)
         | 
| 33 | 
            +
                  labels.map do |label|
         | 
| 34 | 
            +
                    label.downcase.gsub(/[\.\s-]/, '_')
         | 
| 35 | 
            +
                  end.join('-')
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def self.find_file_by_alias(file_alias, search_path, ignored_patterns = nil)
         | 
| 39 | 
            +
                  FileFinder.new(search_path, ignored_patterns).find_file(file_alias)
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def self.replace_tokens(template, variables, token_regexp = nil, token_replacement = nil)
         | 
| 43 | 
            +
                  if token_regexp.nil?
         | 
| 44 | 
            +
                    MustacheTokenHandler.new(template).replace(variables)
         | 
| 45 | 
            +
                  else
         | 
| 46 | 
            +
                    RegexpTokenHandler.new(template, token_regexp, token_replacement).replace(variables)
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              class FileFinder
         | 
| 52 | 
            +
                attr_reader :search_path, :ignored_patterns
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def initialize(search_path, ignored_patterns)
         | 
| 55 | 
            +
                  @search_path = search_path
         | 
| 56 | 
            +
                  @ignored_patterns = ignored_patterns || read_gitignore(search_path)
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                # Finds a file by loosely matching the file name to a scenario name
         | 
| 60 | 
            +
                def find_file(name)
         | 
| 61 | 
            +
                  return name if File.exist? File.expand_path(name, search_path)
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  # Filter out ignored filesFind the first file, not including generated files
         | 
| 64 | 
            +
                  files = potential_files(name).select do |f|
         | 
| 65 | 
            +
                    !ignored? f
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  # Select the shortest path, likely the best match
         | 
| 69 | 
            +
                  file = files.min_by(&:length)
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  fail Errno::ENOENT, "No file was found for #{name} within #{search_path}" if file.nil?
         | 
| 72 | 
            +
                  Psychic::Util.relativize(file, search_path)
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def potential_files(name)
         | 
| 76 | 
            +
                  slugified_name = Psychic::Util.slugify(name)
         | 
| 77 | 
            +
                  glob_string = "#{search_path}/**/*#{slugified_name}*.*"
         | 
| 78 | 
            +
                  potential_files = Dir.glob(glob_string, File::FNM_CASEFOLD)
         | 
| 79 | 
            +
                  potential_files.concat Dir.glob(glob_string.gsub('_', '-'), File::FNM_CASEFOLD)
         | 
| 80 | 
            +
                  potential_files.concat Dir.glob(glob_string.gsub('_', ''), File::FNM_CASEFOLD)
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                private
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                # @api private
         | 
| 86 | 
            +
                def read_gitignore(dir)
         | 
| 87 | 
            +
                  gitignore_file = "#{dir}/.gitignore"
         | 
| 88 | 
            +
                  File.read(gitignore_file)
         | 
| 89 | 
            +
                rescue
         | 
| 90 | 
            +
                  ''
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                # @api private
         | 
| 94 | 
            +
                def ignored?(target_file)
         | 
| 95 | 
            +
                  # Trying to match the git ignore rules but there's some discrepencies.
         | 
| 96 | 
            +
                  ignored_patterns.split.find do |pattern|
         | 
| 97 | 
            +
                    # if git ignores a folder, we should ignore all files it contains
         | 
| 98 | 
            +
                    pattern = "#{pattern}**" if pattern[-1] == '/'
         | 
| 99 | 
            +
                    started_with_slash = pattern.start_with? '/'
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                    pattern.gsub!(%r{\A/}, '') # remove leading slashes since we're searching from root
         | 
| 102 | 
            +
                    file = Psychic::Util.relativize(target_file, search_path)
         | 
| 103 | 
            +
                    ignored = file.fnmatch? pattern
         | 
| 104 | 
            +
                    ignored || (file.fnmatch? "**/#{pattern}" unless started_with_slash)
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            # coding: utf-8
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require 'psychic/runner/version'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
              spec.name          = 'psychic-runner'
         | 
| 8 | 
            +
              spec.version       = Psychic::Runner::VERSION
         | 
| 9 | 
            +
              spec.authors       = ['Max Lincoln']
         | 
| 10 | 
            +
              spec.email         = ['max@devopsy.com']
         | 
| 11 | 
            +
              spec.summary       = 'Psychic runs anything.'
         | 
| 12 | 
            +
              spec.description   = 'Provides cross-project aliases for running tasks or similar code samples.'
         | 
| 13 | 
            +
              # spec.homepage      = ''
         | 
| 14 | 
            +
              spec.license       = 'MIT'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              spec.files         = `git ls-files -z`.split("\x0")
         | 
| 17 | 
            +
              spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
         | 
| 18 | 
            +
              spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
         | 
| 19 | 
            +
              spec.require_paths = ['lib']
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              spec.add_dependency 'thor', '~> 0.19'
         | 
| 22 | 
            +
              spec.add_dependency 'mixlib-shellout', '~> 1.3' # Used for MRI
         | 
| 23 | 
            +
              # spec.add_dependency "buff-shell_out", "~> 0.1"  # Used for JRuby
         | 
| 24 | 
            +
              spec.add_dependency 'mustache', '~> 0.99'
         | 
| 25 | 
            +
              spec.add_development_dependency 'bundler', '~> 1.7'
         | 
| 26 | 
            +
              spec.add_development_dependency 'rake', '~> 10.0'
         | 
| 27 | 
            +
              spec.add_development_dependency 'rake-notes'
         | 
| 28 | 
            +
              spec.add_development_dependency 'simplecov'
         | 
| 29 | 
            +
              spec.add_development_dependency 'rspec', '~> 3.0'
         | 
| 30 | 
            +
              spec.add_development_dependency 'rubocop', '~> 0.18', '<= 0.27'
         | 
| 31 | 
            +
              spec.add_development_dependency 'rubocop-rspec', '~> 1.2'
         | 
| 32 | 
            +
              spec.add_development_dependency 'aruba'
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              class Runner
         | 
| 3 | 
            +
                module Cold
         | 
| 4 | 
            +
                  RSpec.describe ShellScriptRunner do
         | 
| 5 | 
            +
                    let(:shell) { Psychic::Shell.shell = double('shell') }
         | 
| 6 | 
            +
                    subject { described_class.new(cwd: current_dir) }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    shared_context 'with scripts/*.sh files' do
         | 
| 9 | 
            +
                      before(:each) do
         | 
| 10 | 
            +
                        write_file 'scripts/bootstrap.sh', ''
         | 
| 11 | 
            +
                        write_file 'scripts/compile.sh', ''
         | 
| 12 | 
            +
                        write_file 'scripts/foo.ps1', ''
         | 
| 13 | 
            +
                      end
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    shared_context 'with scripts/* (no extension) files' do
         | 
| 17 | 
            +
                      before(:each) do
         | 
| 18 | 
            +
                        write_file 'scripts/bootstrap', ''
         | 
| 19 | 
            +
                        write_file 'scripts/compile', ''
         | 
| 20 | 
            +
                        write_file 'scripts/.foo', ''
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    describe 'respond_to?' do
         | 
| 25 | 
            +
                      shared_examples 'detects matching scripts' do
         | 
| 26 | 
            +
                        it 'returns true if a matching script exists' do
         | 
| 27 | 
            +
                          expect(subject.respond_to? :bootstrap).to be true
         | 
| 28 | 
            +
                          expect(subject.respond_to? :compile).to be true
         | 
| 29 | 
            +
                        end
         | 
| 30 | 
            +
                        it 'returns false if a matching script does not exists' do
         | 
| 31 | 
            +
                          expect(subject.respond_to? :foo).to be false
         | 
| 32 | 
            +
                          expect(subject.respond_to? :bar).to be false
         | 
| 33 | 
            +
                        end
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      context 'with scripts/*.sh files' do
         | 
| 37 | 
            +
                        include_context 'with scripts/*.sh files' do
         | 
| 38 | 
            +
                          include_examples 'detects matching scripts'
         | 
| 39 | 
            +
                        end
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      context 'with scripts/* (no extension) files' do
         | 
| 43 | 
            +
                        include_context 'with scripts/* (no extension) files' do
         | 
| 44 | 
            +
                          include_examples 'detects matching scripts'
         | 
| 45 | 
            +
                        end
         | 
| 46 | 
            +
                      end
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    describe '#method_missing' do
         | 
| 50 | 
            +
                      context 'matching a task' do
         | 
| 51 | 
            +
                        context 'with scripts/*.sh files' do
         | 
| 52 | 
            +
                          include_context 'with scripts/*.sh files' do
         | 
| 53 | 
            +
                            it 'executes the script command' do
         | 
| 54 | 
            +
                              expect(shell).to receive(:execute).with('./scripts/bootstrap.sh', cwd: current_dir)
         | 
| 55 | 
            +
                              subject.bootstrap
         | 
| 56 | 
            +
                            end
         | 
| 57 | 
            +
                          end
         | 
| 58 | 
            +
                        end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                        context 'with scripts/* (no extension) files' do
         | 
| 61 | 
            +
                          include_context 'with scripts/* (no extension) files' do
         | 
| 62 | 
            +
                            it 'executes the script command' do
         | 
| 63 | 
            +
                              expect(shell).to receive(:execute).with('./scripts/bootstrap', cwd: current_dir)
         | 
| 64 | 
            +
                              subject.bootstrap
         | 
| 65 | 
            +
                            end
         | 
| 66 | 
            +
                          end
         | 
| 67 | 
            +
                        end
         | 
| 68 | 
            +
                      end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                      context 'not matching a task' do
         | 
| 71 | 
            +
                        it 'raises an error' do
         | 
| 72 | 
            +
                          # Use foo to ensure it doesn't match ps1 or hidden (. prefixed) files
         | 
| 73 | 
            +
                          expect { subject.foo }.to raise_error(NoMethodError)
         | 
| 74 | 
            +
                        end
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
            end
         | 
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              class Runner
         | 
| 3 | 
            +
                RSpec.describe HotRunner do
         | 
| 4 | 
            +
                  let(:task_map) do
         | 
| 5 | 
            +
                    {
         | 
| 6 | 
            +
                      'bootstrap' => 'foo',
         | 
| 7 | 
            +
                      'compile'   => 'bar',
         | 
| 8 | 
            +
                      'execute'   => 'baz'
         | 
| 9 | 
            +
                    }
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                  let(:shell) { Psychic::Shell.shell = double('shell') }
         | 
| 12 | 
            +
                  subject { described_class.new(cwd: current_dir, hints: task_map) }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  shared_examples 'runs tasks' do
         | 
| 15 | 
            +
                    describe 'respond_to?' do
         | 
| 16 | 
            +
                      it 'returns true for task ids' do
         | 
| 17 | 
            +
                        task_map.each_key do |key|
         | 
| 18 | 
            +
                          expect(subject.respond_to? key).to be true
         | 
| 19 | 
            +
                        end
         | 
| 20 | 
            +
                      end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      it 'returns false for anything else' do
         | 
| 23 | 
            +
                        expect(subject.respond_to? 'max').to be false
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    describe '#method_missing' do
         | 
| 28 | 
            +
                      context 'matching a task' do
         | 
| 29 | 
            +
                        it 'executes the task command' do
         | 
| 30 | 
            +
                          expect(shell).to receive(:execute).with('foo', cwd: current_dir)
         | 
| 31 | 
            +
                          subject.bootstrap
         | 
| 32 | 
            +
                        end
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                      context 'not matching a task' do
         | 
| 36 | 
            +
                        it 'raises an error' do
         | 
| 37 | 
            +
                          expect { subject.spin_around }.to raise_error(NoMethodError)
         | 
| 38 | 
            +
                        end
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  context 'task map stored in psychic-hints.yml' do
         | 
| 44 | 
            +
                    let(:hints) do
         | 
| 45 | 
            +
                      { 'tasks' => task_map }
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                    before(:each) do
         | 
| 48 | 
            +
                      write_file 'psychic-hints.yml', YAML.dump(hints)
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                    subject { described_class.new(cwd: current_dir) }
         | 
| 51 | 
            +
                    include_examples 'runs tasks'
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  context 'hints passed as a parameter' do
         | 
| 55 | 
            +
                    let(:hints) do
         | 
| 56 | 
            +
                      { 'tasks' => task_map }
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                    subject { described_class.new(cwd: current_dir, hints: hints) }
         | 
| 59 | 
            +
                    include_examples 'runs tasks'
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Psychic
         | 
| 4 | 
            +
              RSpec.describe Runner do
         | 
| 5 | 
            +
                subject { described_class.new(cwd: current_dir) }
         | 
| 6 | 
            +
                context 'when psychic-hints.yml exists' do
         | 
| 7 | 
            +
                  let(:hints) do
         | 
| 8 | 
            +
                    {
         | 
| 9 | 
            +
                      'tasks' =>
         | 
| 10 | 
            +
                      {
         | 
| 11 | 
            +
                        'bootstrap' => 'foo',
         | 
| 12 | 
            +
                        'compile'   => 'bar',
         | 
| 13 | 
            +
                        'execute'   => 'baz'
         | 
| 14 | 
            +
                      }
         | 
| 15 | 
            +
                    }
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  before(:each) do
         | 
| 19 | 
            +
                    write_file 'psychic-hints.yml', YAML.dump(hints)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  describe 'initialize' do
         | 
| 23 | 
            +
                    it 'should create a HotRunner for the specified directory' do
         | 
| 24 | 
            +
                      expect(subject.hot_runner).to be_an_instance_of(Psychic::Runner::HotRunner)
         | 
| 25 | 
            +
                      expect(subject.cwd).to eq(current_dir)
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                context 'when scripts/* exist' do
         | 
| 31 | 
            +
                  before(:each) do
         | 
| 32 | 
            +
                    write_file 'scripts/bootstrap.sh', ''
         | 
| 33 | 
            +
                    write_file 'scripts/foo.sh', ''
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  describe 'initialize' do
         | 
| 37 | 
            +
                    it 'should create a cold runner for ShellScriptRunner' do
         | 
| 38 | 
            +
                      expect(subject.cold_runners).to include(
         | 
| 39 | 
            +
                        an_instance_of(Psychic::Runner::Cold::ShellScriptRunner)
         | 
| 40 | 
            +
                      )
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            require 'simplecov'
         | 
| 2 | 
            +
            SimpleCov.start
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'rspec'
         | 
| 5 | 
            +
            require 'psychic/runner'
         | 
| 6 | 
            +
            require 'aruba'
         | 
| 7 | 
            +
            require 'aruba/api'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # Config required for project
         | 
| 10 | 
            +
            RSpec.configure do | config |
         | 
| 11 | 
            +
              config.include Aruba::Api
         | 
| 12 | 
            +
              config.before(:example) do
         | 
| 13 | 
            +
                @aruba_timeout_seconds = 30
         | 
| 14 | 
            +
                clean_current_dir
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            # Configs recommended by RSpec
         | 
| 19 | 
            +
            RSpec.configure do |config|
         | 
| 20 | 
            +
              config.warnings = true
         | 
| 21 | 
            +
              config.disable_monkey_patching!
         | 
| 22 | 
            +
              config.filter_run :focus
         | 
| 23 | 
            +
              config.run_all_when_everything_filtered = true
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              config.expect_with :rspec do |expectations|
         | 
| 26 | 
            +
                # This option will default to `true` in RSpec 4.
         | 
| 27 | 
            +
                expectations.include_chain_clauses_in_custom_matcher_descriptions = true
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              config.mock_with :rspec do |mocks|
         | 
| 31 | 
            +
                # Prevents you from mocking or stubbing a method that does not exist on
         | 
| 32 | 
            +
                # a real object. This is generally recommended, and will default to
         | 
| 33 | 
            +
                # `true` in RSpec 4.
         | 
| 34 | 
            +
                mocks.verify_partial_doubles = true
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              if config.files_to_run.one?
         | 
| 38 | 
            +
                # Use the documentation formatter for detailed output,
         | 
| 39 | 
            +
                # unless a formatter has already been configured
         | 
| 40 | 
            +
                # (e.g. via a command-line flag).
         | 
| 41 | 
            +
                config.default_formatter = 'doc'
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              config.profile_examples = 10
         | 
| 45 | 
            +
              config.order = :random
         | 
| 46 | 
            +
              Kernel.srand config.seed
         | 
| 47 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,239 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: psychic-runner
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.2
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Max Lincoln
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2014-12-03 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: thor
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '0.19'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '0.19'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: mixlib-shellout
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '1.3'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '1.3'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: mustache
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '0.99'
         | 
| 48 | 
            +
              type: :runtime
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0.99'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: bundler
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '1.7'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '1.7'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: rake
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '10.0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - "~>"
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '10.0'
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: rake-notes
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - ">="
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: '0'
         | 
| 90 | 
            +
              type: :development
         | 
| 91 | 
            +
              prerelease: false
         | 
| 92 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            +
                requirements:
         | 
| 94 | 
            +
                - - ">="
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: '0'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: simplecov
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - ">="
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '0'
         | 
| 104 | 
            +
              type: :development
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - ">="
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '0'
         | 
| 111 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 112 | 
            +
              name: rspec
         | 
| 113 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 114 | 
            +
                requirements:
         | 
| 115 | 
            +
                - - "~>"
         | 
| 116 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            +
                    version: '3.0'
         | 
| 118 | 
            +
              type: :development
         | 
| 119 | 
            +
              prerelease: false
         | 
| 120 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 121 | 
            +
                requirements:
         | 
| 122 | 
            +
                - - "~>"
         | 
| 123 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 124 | 
            +
                    version: '3.0'
         | 
| 125 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 126 | 
            +
              name: rubocop
         | 
| 127 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 128 | 
            +
                requirements:
         | 
| 129 | 
            +
                - - "~>"
         | 
| 130 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 131 | 
            +
                    version: '0.18'
         | 
| 132 | 
            +
                - - "<="
         | 
| 133 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 134 | 
            +
                    version: '0.27'
         | 
| 135 | 
            +
              type: :development
         | 
| 136 | 
            +
              prerelease: false
         | 
| 137 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 138 | 
            +
                requirements:
         | 
| 139 | 
            +
                - - "~>"
         | 
| 140 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 141 | 
            +
                    version: '0.18'
         | 
| 142 | 
            +
                - - "<="
         | 
| 143 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 144 | 
            +
                    version: '0.27'
         | 
| 145 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 146 | 
            +
              name: rubocop-rspec
         | 
| 147 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 148 | 
            +
                requirements:
         | 
| 149 | 
            +
                - - "~>"
         | 
| 150 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 151 | 
            +
                    version: '1.2'
         | 
| 152 | 
            +
              type: :development
         | 
| 153 | 
            +
              prerelease: false
         | 
| 154 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 155 | 
            +
                requirements:
         | 
| 156 | 
            +
                - - "~>"
         | 
| 157 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 158 | 
            +
                    version: '1.2'
         | 
| 159 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 160 | 
            +
              name: aruba
         | 
| 161 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 162 | 
            +
                requirements:
         | 
| 163 | 
            +
                - - ">="
         | 
| 164 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 165 | 
            +
                    version: '0'
         | 
| 166 | 
            +
              type: :development
         | 
| 167 | 
            +
              prerelease: false
         | 
| 168 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 169 | 
            +
                requirements:
         | 
| 170 | 
            +
                - - ">="
         | 
| 171 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 172 | 
            +
                    version: '0'
         | 
| 173 | 
            +
            description: Provides cross-project aliases for running tasks or similar code samples.
         | 
| 174 | 
            +
            email:
         | 
| 175 | 
            +
            - max@devopsy.com
         | 
| 176 | 
            +
            executables:
         | 
| 177 | 
            +
            - psychic
         | 
| 178 | 
            +
            extensions: []
         | 
| 179 | 
            +
            extra_rdoc_files: []
         | 
| 180 | 
            +
            files:
         | 
| 181 | 
            +
            - ".gitignore"
         | 
| 182 | 
            +
            - ".rspec"
         | 
| 183 | 
            +
            - ".rubocop.yml"
         | 
| 184 | 
            +
            - ".rubocop_todo.yml"
         | 
| 185 | 
            +
            - ".travis.yml"
         | 
| 186 | 
            +
            - Gemfile
         | 
| 187 | 
            +
            - LICENSE.txt
         | 
| 188 | 
            +
            - README.md
         | 
| 189 | 
            +
            - Rakefile
         | 
| 190 | 
            +
            - bin/psychic
         | 
| 191 | 
            +
            - lib/psychic/cli.rb
         | 
| 192 | 
            +
            - lib/psychic/commands/exec.rb
         | 
| 193 | 
            +
            - lib/psychic/logger.rb
         | 
| 194 | 
            +
            - lib/psychic/runner.rb
         | 
| 195 | 
            +
            - lib/psychic/runner/base_runner.rb
         | 
| 196 | 
            +
            - lib/psychic/runner/cold/shell_script_runner.rb
         | 
| 197 | 
            +
            - lib/psychic/runner/cold_runner_registry.rb
         | 
| 198 | 
            +
            - lib/psychic/runner/hot_runner.rb
         | 
| 199 | 
            +
            - lib/psychic/runner/sample_runner.rb
         | 
| 200 | 
            +
            - lib/psychic/runner/version.rb
         | 
| 201 | 
            +
            - lib/psychic/shell.rb
         | 
| 202 | 
            +
            - lib/psychic/shell/execution_result.rb
         | 
| 203 | 
            +
            - lib/psychic/shell/mixlib_shellout_executor.rb
         | 
| 204 | 
            +
            - lib/psychic/tokens.rb
         | 
| 205 | 
            +
            - lib/psychic/util.rb
         | 
| 206 | 
            +
            - psychic-runner.gemspec
         | 
| 207 | 
            +
            - spec/psychic/runner/cold/shell_script_runner_spec.rb
         | 
| 208 | 
            +
            - spec/psychic/runner/hot_runner_spec.rb
         | 
| 209 | 
            +
            - spec/psychic/runner_spec.rb
         | 
| 210 | 
            +
            - spec/spec_helper.rb
         | 
| 211 | 
            +
            homepage: 
         | 
| 212 | 
            +
            licenses:
         | 
| 213 | 
            +
            - MIT
         | 
| 214 | 
            +
            metadata: {}
         | 
| 215 | 
            +
            post_install_message: 
         | 
| 216 | 
            +
            rdoc_options: []
         | 
| 217 | 
            +
            require_paths:
         | 
| 218 | 
            +
            - lib
         | 
| 219 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 220 | 
            +
              requirements:
         | 
| 221 | 
            +
              - - ">="
         | 
| 222 | 
            +
                - !ruby/object:Gem::Version
         | 
| 223 | 
            +
                  version: '0'
         | 
| 224 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 225 | 
            +
              requirements:
         | 
| 226 | 
            +
              - - ">="
         | 
| 227 | 
            +
                - !ruby/object:Gem::Version
         | 
| 228 | 
            +
                  version: '0'
         | 
| 229 | 
            +
            requirements: []
         | 
| 230 | 
            +
            rubyforge_project: 
         | 
| 231 | 
            +
            rubygems_version: 2.4.2
         | 
| 232 | 
            +
            signing_key: 
         | 
| 233 | 
            +
            specification_version: 4
         | 
| 234 | 
            +
            summary: Psychic runs anything.
         | 
| 235 | 
            +
            test_files:
         | 
| 236 | 
            +
            - spec/psychic/runner/cold/shell_script_runner_spec.rb
         | 
| 237 | 
            +
            - spec/psychic/runner/hot_runner_spec.rb
         | 
| 238 | 
            +
            - spec/psychic/runner_spec.rb
         | 
| 239 | 
            +
            - spec/spec_helper.rb
         |