psychic-runner 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/bin/psychic +1 -1
- data/lib/psychic/cli.rb +8 -1
- data/lib/psychic/output_helper.rb +84 -0
- data/lib/psychic/runner.rb +88 -16
- data/lib/psychic/runner/base_runner.rb +89 -36
- data/lib/psychic/runner/cli.rb +80 -25
- data/lib/psychic/runner/code_helper.rb +131 -0
- data/lib/psychic/runner/code_sample.rb +70 -0
- data/lib/psychic/runner/factories/ruby_factories.rb +16 -0
- data/lib/psychic/runner/{cold/shell_script_runner.rb → factories/shell_script_factories.rb} +4 -4
- data/lib/psychic/runner/{hot_runner.rb → hot_read_task_factory.rb} +4 -3
- data/lib/psychic/runner/magic_task_factory.rb +95 -0
- data/lib/psychic/runner/sample_finder.rb +36 -0
- data/lib/psychic/runner/sample_runner.rb +7 -15
- data/lib/psychic/runner/task_factory_registry.rb +31 -0
- data/lib/psychic/runner/version.rb +1 -1
- data/lib/psychic/shell/mixlib_shellout_executor.rb +3 -1
- data/lib/psychic/task.rb +14 -0
- data/spec/psychic/runner/factories/bundler_detector_spec.rb +90 -0
- data/spec/psychic/runner/{cold → factories}/shell_script_runner_spec.rb +10 -10
- data/spec/psychic/runner/{hot_runner_spec.rb → hot_read_task_factory_spec.rb} +7 -8
- data/spec/psychic/runner/sample_finder_spec.rb +34 -0
- data/spec/psychic/runner_spec.rb +7 -5
- metadata +20 -9
- data/lib/psychic/runner/cold_runner_registry.rb +0 -31
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: a3af75e80b40bfaa12b7fdf0c0321d653ab56b86
         | 
| 4 | 
            +
              data.tar.gz: ed65f470ce7e85d0f62b329c34ecd476818c9c3e
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 03565dc8cc94cca3682ce30a9e6e3967f7b682882591d5005d08f11c6722141bfad92a1642456611ce4ebe3ba0dec91d4131505a4f1b51c59a487778444b53fa
         | 
| 7 | 
            +
              data.tar.gz: 48e7bd41635a48408e4b6a103a4e9688135e2ee75d82c7f81da9042f144b285bb1835aff15a549a6f89820d478f29df7766eade9c4647a84882a1e92acf3b8ba
         | 
    
        data/Gemfile
    CHANGED
    
    
    
        data/bin/psychic
    CHANGED
    
    
    
        data/lib/psychic/cli.rb
    CHANGED
    
    | @@ -11,7 +11,14 @@ module Psychic | |
| 11 11 |  | 
| 12 12 | 
             
                  def start(given_args = ARGV, config = {})
         | 
| 13 13 | 
             
                    if given_args && (split_pos = given_args.index('--'))
         | 
| 14 | 
            -
                      @extra_args = given_args.slice(split_pos + 1, given_args.length)
         | 
| 14 | 
            +
                      @extra_args = given_args.slice(split_pos + 1, given_args.length).map do | arg |
         | 
| 15 | 
            +
                        # Restore quotes
         | 
| 16 | 
            +
                        next unless arg.match(/\=/)
         | 
| 17 | 
            +
                        lhs, rhs = arg.split('=')
         | 
| 18 | 
            +
                        lhs = "\"#{lhs}\"" if lhs.match(/\s/)
         | 
| 19 | 
            +
                        rhs = "\"#{rhs}\"" if rhs.match(/\s/)
         | 
| 20 | 
            +
                        [lhs, rhs].join('=')
         | 
| 21 | 
            +
                      end
         | 
| 15 22 | 
             
                      given_args = given_args.slice(0, split_pos)
         | 
| 16 23 | 
             
                    end
         | 
| 17 24 | 
             
                    super given_args, config
         | 
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            module Psychic
         | 
| 2 | 
            +
              module OutputHelper
         | 
| 3 | 
            +
                class StringShell < Thor::Base.shell
         | 
| 4 | 
            +
                  attr_reader :io
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize(*args)
         | 
| 7 | 
            +
                    @io = StringIO.new
         | 
| 8 | 
            +
                    super
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  alias_method :stdout, :io
         | 
| 12 | 
            +
                  alias_method :stderr, :io
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def string
         | 
| 15 | 
            +
                    @io.string
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def can_display_colors?
         | 
| 19 | 
            +
                    # Still capture colors if they can eventually be displayed.
         | 
| 20 | 
            +
                    $stdout.tty?
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def cli
         | 
| 25 | 
            +
                  @cli ||= Thor::Base.shell.new
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def build_string
         | 
| 29 | 
            +
                  old_cli = @cli
         | 
| 30 | 
            +
                  new_cli = @cli = StringShell.new
         | 
| 31 | 
            +
                  yield
         | 
| 32 | 
            +
                  @cli = old_cli
         | 
| 33 | 
            +
                  new_cli.string
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def reformat(string)
         | 
| 37 | 
            +
                  return if string.nil? || string.empty?
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  indent do
         | 
| 40 | 
            +
                    string.gsub(/^/, indent)
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def indent
         | 
| 45 | 
            +
                  @indent_level ||= 0
         | 
| 46 | 
            +
                  if block_given?
         | 
| 47 | 
            +
                    @indent_level += 2
         | 
| 48 | 
            +
                    result = yield
         | 
| 49 | 
            +
                    @indent_level -= 2
         | 
| 50 | 
            +
                    result
         | 
| 51 | 
            +
                  else
         | 
| 52 | 
            +
                    ' ' * @indent_level
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def say(msg)
         | 
| 57 | 
            +
                  cli.say msg if msg
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def status(status, msg = nil, color = :cyan, colwidth = 50)
         | 
| 61 | 
            +
                  msg = yield if block_given?
         | 
| 62 | 
            +
                  cli.say(indent) if indent.length > 0
         | 
| 63 | 
            +
                  status = cli.set_color("#{status}:", color, true)
         | 
| 64 | 
            +
                  # The built-in say_status is right-aligned, we want left-aligned
         | 
| 65 | 
            +
                  cli.say format("%-#{colwidth}s %s", status, msg).rstrip
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                # TODO: Reporters for different formats
         | 
| 69 | 
            +
                def print_table(*args)
         | 
| 70 | 
            +
                  # @reporter.print_table(*args)
         | 
| 71 | 
            +
                  cli.print_table(*args)
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def colorize(string, *args)
         | 
| 75 | 
            +
                  return string unless @reporter.respond_to? :set_color
         | 
| 76 | 
            +
                  # @reporter.set_color(string, *args)
         | 
| 77 | 
            +
                  cli.set_color(string, *args)
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def color_pad(string)
         | 
| 81 | 
            +
                  string + colorize('', :white)
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
            end
         | 
    
        data/lib/psychic/runner.rb
    CHANGED
    
    | @@ -1,38 +1,110 @@ | |
| 1 1 | 
             
            require 'psychic/runner/version'
         | 
| 2 2 |  | 
| 3 | 
            +
            autoload :Thor, 'thor'
         | 
| 3 4 | 
             
            autoload :YAML, 'yaml'
         | 
| 4 5 |  | 
| 5 6 | 
             
            module Psychic
         | 
| 6 7 | 
             
              autoload :Util,   'psychic/util'
         | 
| 7 8 | 
             
              autoload :Logger, 'psychic/logger'
         | 
| 8 9 | 
             
              autoload :Shell,  'psychic/shell'
         | 
| 10 | 
            +
              autoload :OutputHelper, 'psychic/output_helper'
         | 
| 9 11 | 
             
              class Runner
         | 
| 12 | 
            +
                autoload :MagicTaskFactory, 'psychic/runner/magic_task_factory'
         | 
| 10 13 | 
             
                autoload :BaseRunner, 'psychic/runner/base_runner'
         | 
| 14 | 
            +
                autoload :CodeSample, 'psychic/runner/code_sample'
         | 
| 15 | 
            +
                autoload :SampleFinder, 'psychic/runner/sample_finder'
         | 
| 11 16 | 
             
                autoload :SampleRunner, 'psychic/runner/sample_runner'
         | 
| 12 | 
            -
                autoload : | 
| 13 | 
            -
                autoload : | 
| 14 | 
            -
                 | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            +
                autoload :HotReadTaskFactory, 'psychic/runner/hot_read_task_factory'
         | 
| 18 | 
            +
                autoload :TaskFactoryRegistry, 'psychic/runner/task_factory_registry'
         | 
| 19 | 
            +
                class TaskNotImplementedError < NotImplementedError
         | 
| 20 | 
            +
                  def initialize(task_name)
         | 
| 21 | 
            +
                    super("#{self.class} cannot handle task #{task_name}")
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
                TaskFactoryRegistry.autoload_task_factories!
         | 
| 17 25 |  | 
| 18 26 | 
             
                include BaseRunner
         | 
| 19 27 | 
             
                include SampleRunner
         | 
| 20 | 
            -
                attr_reader :runners, : | 
| 28 | 
            +
                attr_reader :runners, :hot_read_task_factory, :task_factories, :sample_factories
         | 
| 21 29 |  | 
| 22 | 
            -
                def initialize(opts = { cwd: Dir.pwd })
         | 
| 30 | 
            +
                def initialize(opts = { cwd: Dir.pwd }) # rubocop:disable Metrics/MethodLength
         | 
| 31 | 
            +
                  # TODO: Will reduce method length after further splitting Runner vs TaskFactory
         | 
| 23 32 | 
             
                  fail 'cwd is required' unless opts[:cwd]
         | 
| 24 | 
            -
                   | 
| 25 | 
            -
                   | 
| 26 | 
            -
                  @ | 
| 27 | 
            -
                   | 
| 28 | 
            -
                   | 
| 29 | 
            -
                   | 
| 33 | 
            +
                  # must be a string on windows...
         | 
| 34 | 
            +
                  opts[:cwd] = Pathname(opts[:cwd]).to_s
         | 
| 35 | 
            +
                  @opts = opts
         | 
| 36 | 
            +
                  init_attr(:cwd) { Dir.pwd }
         | 
| 37 | 
            +
                  init_hints
         | 
| 38 | 
            +
                  init_attr(:logger) { new_logger }
         | 
| 39 | 
            +
                  init_attr(:env) { ENV.to_hash }
         | 
| 40 | 
            +
                  init_attrs :cli, :interactive, :parameter_mode, :restore_mode, :dry_run
         | 
| 41 | 
            +
                  @shell_opts = select_shell_opts
         | 
| 42 | 
            +
                  @parameters = load_parameters(opts[:parameters])
         | 
| 43 | 
            +
                  # super
         | 
| 44 | 
            +
                  @hot_read_task_factory = HotReadTaskFactory.new(opts)
         | 
| 45 | 
            +
                  @sample_finder = SampleFinder.new(opts[:cwd], @hot_read_task_factory.hints['samples'])
         | 
| 46 | 
            +
                  @task_factories = TaskFactoryRegistry.active_task_factories(opts)
         | 
| 47 | 
            +
                  @runners = [@hot_read_task_factory, @task_factories].flatten
         | 
| 48 | 
            +
                  @known_tasks = @runners.flat_map(&:known_tasks).uniq
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def known_samples
         | 
| 52 | 
            +
                  @sample_finder.known_samples
         | 
| 30 53 | 
             
                end
         | 
| 31 54 |  | 
| 32 | 
            -
                def  | 
| 33 | 
            -
                  runner = runners.find { |r| r. | 
| 55 | 
            +
                def task_for(task_name)
         | 
| 56 | 
            +
                  runner = runners.find { |r| r.known_task?(task_name) }
         | 
| 34 57 | 
             
                  return nil unless runner
         | 
| 35 | 
            -
                  runner | 
| 58 | 
            +
                  runner.task_for(task_name)
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                private
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def init_attr(var)
         | 
| 64 | 
            +
                  var_name = "@#{var}"
         | 
| 65 | 
            +
                  var_value = @opts[var]
         | 
| 66 | 
            +
                  var_value = yield if var_value.nil? && block_given?
         | 
| 67 | 
            +
                  instance_variable_set(var_name, var_value)
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def init_attrs(*vars)
         | 
| 71 | 
            +
                  vars.each do | var |
         | 
| 72 | 
            +
                    init_attr var
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def init_hints
         | 
| 77 | 
            +
                  @hints = Psychic::Util.stringified_hash(@opts[:hints] || load_hints || {})
         | 
| 78 | 
            +
                  if @hints['options']
         | 
| 79 | 
            +
                    @opts.merge! Psychic::Util.symbolized_hash(@hints['options'])
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def select_shell_opts
         | 
| 84 | 
            +
                  # Make sure to delete any option that isn't a MixLib::ShellOut option
         | 
| 85 | 
            +
                  @opts.select { |key, _| Psychic::Shell::AVAILABLE_OPTIONS.include? key }
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def load_hints
         | 
| 89 | 
            +
                  hints_file = Dir["#{@cwd}/psychic.{yaml,yml}"].first
         | 
| 90 | 
            +
                  YAML.load(File.read(hints_file)) unless hints_file.nil?
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                def load_parameters(parameters)
         | 
| 94 | 
            +
                  if parameters.nil? || parameters.is_a?(String)
         | 
| 95 | 
            +
                    load_parameters_file(parameters)
         | 
| 96 | 
            +
                  else
         | 
| 97 | 
            +
                    parameters
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                def load_parameters_file(file = nil)
         | 
| 102 | 
            +
                  if file.nil?
         | 
| 103 | 
            +
                    file ||= File.expand_path(DEFAULT_PARAMS_FILE, cwd)
         | 
| 104 | 
            +
                    return {} unless File.exist? file
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
                  parameters = Psychic::Util.replace_tokens(File.read(file), @env)
         | 
| 107 | 
            +
                  YAML.load(parameters)
         | 
| 36 108 | 
             
                end
         | 
| 37 109 | 
             
              end
         | 
| 38 110 | 
             
            end
         | 
| @@ -6,20 +6,41 @@ module Psychic | |
| 6 6 | 
             
                  include Psychic::Shell
         | 
| 7 7 | 
             
                  include Psychic::Logger
         | 
| 8 8 |  | 
| 9 | 
            -
                  attr_reader :known_tasks
         | 
| 10 | 
            -
                  attr_reader :cwd
         | 
| 11 | 
            -
                  attr_reader :env
         | 
| 12 | 
            -
                  attr_reader :hints
         | 
| 9 | 
            +
                  attr_reader :known_tasks, :tasks, :cwd, :env, :hints
         | 
| 13 10 |  | 
| 14 11 | 
             
                  module ClassMethods
         | 
| 15 | 
            -
                     | 
| 12 | 
            +
                    def register_task_factory
         | 
| 13 | 
            +
                      Psychic::Runner::TaskFactoryRegistry.register(self)
         | 
| 14 | 
            +
                    end
         | 
| 16 15 |  | 
| 17 | 
            -
                    def  | 
| 18 | 
            -
                       | 
| 16 | 
            +
                    def magic_file_patterns
         | 
| 17 | 
            +
                      @magic_file_patterns ||= []
         | 
| 19 18 | 
             
                    end
         | 
| 20 19 |  | 
| 21 20 | 
             
                    def magic_file(pattern) # rubocop:disable Style/TrivialAccessors
         | 
| 22 | 
            -
                       | 
| 21 | 
            +
                      magic_file_patterns << pattern
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def magic_env_vars
         | 
| 25 | 
            +
                      @magic_env_vars ||= []
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def magic_env_var(var)
         | 
| 29 | 
            +
                      magic_env_vars << var
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    def known_tasks
         | 
| 33 | 
            +
                      @known_tasks ||= []
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def tasks
         | 
| 37 | 
            +
                      @tasks ||= {}
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    def task(name, &block)
         | 
| 41 | 
            +
                      name = name.to_s
         | 
| 42 | 
            +
                      tasks[name] = block
         | 
| 43 | 
            +
                      known_tasks << name
         | 
| 23 44 | 
             
                    end
         | 
| 24 45 | 
             
                  end
         | 
| 25 46 |  | 
| @@ -27,53 +48,54 @@ module Psychic | |
| 27 48 | 
             
                    base.extend(ClassMethods)
         | 
| 28 49 | 
             
                  end
         | 
| 29 50 |  | 
| 30 | 
            -
                  def initialize(opts = {} | 
| 31 | 
            -
                    @ | 
| 32 | 
            -
                     | 
| 33 | 
            -
                     | 
| 34 | 
            -
             | 
| 35 | 
            -
                     | 
| 36 | 
            -
                     | 
| 37 | 
            -
                     | 
| 51 | 
            +
                  def initialize(opts = {})
         | 
| 52 | 
            +
                    @opts = opts
         | 
| 53 | 
            +
                    init_attr(:cwd) { Dir.pwd }
         | 
| 54 | 
            +
                    init_hints
         | 
| 55 | 
            +
                    init_attr(:known_tasks) { self.class.known_tasks }
         | 
| 56 | 
            +
                    init_attr(:tasks) { self.class.tasks }
         | 
| 57 | 
            +
                    init_attr(:logger) { new_logger }
         | 
| 58 | 
            +
                    init_attr(:env) { ENV.to_hash }
         | 
| 59 | 
            +
                    init_attrs :cli, :interactive, :parameter_mode, :restore_mode, :dry_run
         | 
| 60 | 
            +
                    @shell_opts = select_shell_opts
         | 
| 38 61 | 
             
                    @parameters = load_parameters(opts[:parameters])
         | 
| 39 | 
            -
                    @cli, @interactive_mode, @parameter_mode, @restore_mode, @dry_run = opts.values_at(
         | 
| 40 | 
            -
                      :cli, :interactive, :parameter_mode, :restore_mode, :dry_run)
         | 
| 41 | 
            -
                    # Make sure to delete any option that isn't a MixLib::ShellOut option
         | 
| 42 | 
            -
                    @shell_opts = opts.select { |key, _| Psychic::Shell::AVAILABLE_OPTIONS.include? key }
         | 
| 43 62 | 
             
                  end
         | 
| 44 63 |  | 
| 45 | 
            -
                  def  | 
| 46 | 
            -
                     | 
| 47 | 
            -
                    super
         | 
| 64 | 
            +
                  def known_task?(task_name)
         | 
| 65 | 
            +
                    known_tasks.include?(task_name.to_s)
         | 
| 48 66 | 
             
                  end
         | 
| 49 67 |  | 
| 50 | 
            -
                  def  | 
| 51 | 
            -
                     | 
| 52 | 
            -
                  rescue Psychic::Runner::TaskNotImplementedError
         | 
| 53 | 
            -
                    super
         | 
| 68 | 
            +
                  def task_for(task_name)
         | 
| 69 | 
            +
                    tasks[task_name] if tasks.include? task_name
         | 
| 54 70 | 
             
                  end
         | 
| 55 71 |  | 
| 56 | 
            -
                  # Reserved words
         | 
| 57 | 
            -
             | 
| 58 72 | 
             
                  def execute(command, *args)
         | 
| 59 73 | 
             
                    full_cmd = [command, *args].join(' ')
         | 
| 60 74 | 
             
                    logger.info("Executing #{full_cmd}")
         | 
| 61 75 | 
             
                    shell.execute(full_cmd, @shell_opts) unless dry_run?
         | 
| 62 76 | 
             
                  end
         | 
| 63 77 |  | 
| 64 | 
            -
                  def  | 
| 65 | 
            -
                    task_name =  | 
| 66 | 
            -
                     | 
| 78 | 
            +
                  def build_task(task_name, *_args)
         | 
| 79 | 
            +
                    task_name = task_name.to_s
         | 
| 80 | 
            +
                    task = task_for(task_name)
         | 
| 81 | 
            +
                    task = task.call if task.respond_to? :call
         | 
| 82 | 
            +
                    fail Psychic::Runner::TaskNotImplementedError, task_name if task.nil?
         | 
| 83 | 
            +
                    task
         | 
| 67 84 | 
             
                  end
         | 
| 68 85 |  | 
| 69 | 
            -
                  def execute_task( | 
| 70 | 
            -
                    command =  | 
| 71 | 
            -
                    fail Psychic::Runner::TaskNotImplementedError if command.nil?
         | 
| 86 | 
            +
                  def execute_task(task_name, *args)
         | 
| 87 | 
            +
                    command = build_task(task_name, *args)
         | 
| 72 88 | 
             
                    execute(command, *args)
         | 
| 73 89 | 
             
                  end
         | 
| 74 90 |  | 
| 75 91 | 
             
                  def active?
         | 
| 76 | 
            -
                    self.class. | 
| 92 | 
            +
                    self.class.magic_file_patterns.each do | pattern |
         | 
| 93 | 
            +
                      return true unless Dir["#{@cwd}/#{pattern}"].empty?
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
                    self.class.magic_env_vars.each do | var |
         | 
| 96 | 
            +
                      return true if ENV[var]
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
                    false
         | 
| 77 99 | 
             
                  end
         | 
| 78 100 |  | 
| 79 101 | 
             
                  def dry_run?
         | 
| @@ -82,6 +104,31 @@ module Psychic | |
| 82 104 |  | 
| 83 105 | 
             
                  private
         | 
| 84 106 |  | 
| 107 | 
            +
                  def init_attr(var)
         | 
| 108 | 
            +
                    var_name = "@#{var}"
         | 
| 109 | 
            +
                    var_value = @opts[var]
         | 
| 110 | 
            +
                    var_value = yield if var_value.nil? && block_given?
         | 
| 111 | 
            +
                    instance_variable_set(var_name, var_value)
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  def init_attrs(*vars)
         | 
| 115 | 
            +
                    vars.each do | var |
         | 
| 116 | 
            +
                      init_attr var
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  def init_hints
         | 
| 121 | 
            +
                    @hints = Psychic::Util.stringified_hash(@opts[:hints] || load_hints || {})
         | 
| 122 | 
            +
                    if @hints['options']
         | 
| 123 | 
            +
                      @opts.merge! Psychic::Util.symbolized_hash(@hints['options'])
         | 
| 124 | 
            +
                    end
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  def select_shell_opts
         | 
| 128 | 
            +
                    # Make sure to delete any option that isn't a MixLib::ShellOut option
         | 
| 129 | 
            +
                    @opts.select { |key, _| Psychic::Shell::AVAILABLE_OPTIONS.include? key }
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
             | 
| 85 132 | 
             
                  def load_hints
         | 
| 86 133 | 
             
                    hints_file = Dir["#{@cwd}/psychic.{yaml,yml}"].first
         | 
| 87 134 | 
             
                    YAML.load(File.read(hints_file)) unless hints_file.nil?
         | 
| @@ -103,6 +150,12 @@ module Psychic | |
| 103 150 | 
             
                    parameters = Psychic::Util.replace_tokens(File.read(file), @env)
         | 
| 104 151 | 
             
                    YAML.load(parameters)
         | 
| 105 152 | 
             
                  end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                  # Blame Ruby's flatten and Array(...) behavior...
         | 
| 155 | 
            +
                  def to_ary
         | 
| 156 | 
            +
                    nil
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
                  alias_method :to_a, :to_ary
         | 
| 106 159 | 
             
                end
         | 
| 107 160 | 
             
              end
         | 
| 108 161 | 
             
            end
         | 
    
        data/lib/psychic/runner/cli.rb
    CHANGED
    
    | @@ -1,10 +1,32 @@ | |
| 1 | 
            +
            require 'psychic/cli'
         | 
| 1 2 | 
             
            require 'psychic/runner'
         | 
| 2 3 |  | 
| 4 | 
            +
            # rubocop:disable Metrics/LineLength
         | 
| 5 | 
            +
             | 
| 3 6 | 
             
            module Psychic
         | 
| 4 | 
            -
               | 
| 5 | 
            -
                class  | 
| 7 | 
            +
              class Runner
         | 
| 8 | 
            +
                class RunnerCLI < Psychic::CLI
         | 
| 9 | 
            +
                  no_commands do
         | 
| 10 | 
            +
                    def runner
         | 
| 11 | 
            +
                      @runner ||= setup_runner
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    def setup_runner
         | 
| 15 | 
            +
                      runner_opts = { cwd: Dir.pwd, cli: shell, parameters: options.parameters }
         | 
| 16 | 
            +
                      runner_opts.merge!(Util.symbolized_hash(options))
         | 
| 17 | 
            +
                      Psychic::Runner.new(runner_opts)
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                class CLI < RunnerCLI
         | 
| 6 23 | 
             
                  desc 'task <name>', 'Executes any task by name'
         | 
| 7 | 
            -
                   | 
| 24 | 
            +
                  method_option :list, aliases: '-l', desc: 'List known tasks'
         | 
| 25 | 
            +
                  method_option :verbose, aliases: '-v', desc: 'Verbose: display more details'
         | 
| 26 | 
            +
                  method_option :cwd, desc: 'Working directory for detecting and running commands'
         | 
| 27 | 
            +
                  def task(task_name = nil)
         | 
| 28 | 
            +
                    return list_tasks if options[:list]
         | 
| 29 | 
            +
                    abort 'You must specify a task name, run with -l for a list of known tasks' unless task_name
         | 
| 8 30 | 
             
                    result = runner.execute_task(task_name, *extra_args)
         | 
| 9 31 | 
             
                    result.error!
         | 
| 10 32 | 
             
                    say_status :success, task_name
         | 
| @@ -15,33 +37,22 @@ module Psychic | |
| 15 37 |  | 
| 16 38 | 
             
                  BUILT_IN_TASKS.each do |task_name|
         | 
| 17 39 | 
             
                    desc task_name, "Executes the #{task_name} task"
         | 
| 40 | 
            +
                    method_option :verbose, aliases: '-v', desc: 'Verbose: display more details'
         | 
| 41 | 
            +
                    method_option :cwd, desc: 'Working directory for detecting and running commands'
         | 
| 18 42 | 
             
                    define_method(task_name) do
         | 
| 19 43 | 
             
                      task(task_name)
         | 
| 20 44 | 
             
                    end
         | 
| 21 45 | 
             
                  end
         | 
| 22 46 |  | 
| 23 47 | 
             
                  desc 'sample <name>', 'Executes a code sample'
         | 
| 24 | 
            -
                   | 
| 48 | 
            +
                  method_option :verbose, aliases: '-v', desc: 'Verbose: display more details'
         | 
| 49 | 
            +
                  method_option :cwd, desc: 'Working directory for detecting and running commands'
         | 
| 25 50 | 
             
                  method_option :interactive, desc: 'Prompt for parameters?', enum: %w(always missing), lazy_default: 'missing'
         | 
| 26 51 | 
             
                  method_option :parameters, desc: 'YAML file containing key/value parameters. Default: psychic-parameters.yaml'
         | 
| 27 52 | 
             
                  method_option :parameter_mode, desc: 'How should the parameters be passed?', enum: %w(tokens arguments env)
         | 
| 28 53 | 
             
                  method_option :dry_run, desc: 'Do not execute - just show what command would be run', lazy_default: true
         | 
| 29 | 
            -
                   | 
| 30 | 
            -
             | 
| 31 | 
            -
                    sample_names.each do | sample_name |
         | 
| 32 | 
            -
                      say_status :executing, sample_name
         | 
| 33 | 
            -
                      begin
         | 
| 34 | 
            -
                        run_sample sample_name
         | 
| 35 | 
            -
                      rescue Errno::ENOENT
         | 
| 36 | 
            -
                        say_status :failed, "No code sample found for #{sample_name}", :red
         | 
| 37 | 
            -
                        # TODO: Fail on missing? Fail fast?
         | 
| 38 | 
            -
                      end
         | 
| 39 | 
            -
                    end
         | 
| 40 | 
            -
                  end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  private
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                  def run_sample(sample_name)
         | 
| 54 | 
            +
                  def sample(sample_name = nil)
         | 
| 55 | 
            +
                    abort 'You must specify a sample name, run `psychic list samples` for a list of known samples' unless sample_name
         | 
| 45 56 | 
             
                    result = runner.run_sample(sample_name, *extra_args)
         | 
| 46 57 | 
             
                    if options.dry_run
         | 
| 47 58 | 
             
                      say_status :dry_run, sample_name
         | 
| @@ -51,12 +62,56 @@ module Psychic | |
| 51 62 | 
             
                    end
         | 
| 52 63 | 
             
                  end
         | 
| 53 64 |  | 
| 54 | 
            -
                   | 
| 55 | 
            -
                     | 
| 56 | 
            -
             | 
| 57 | 
            -
                     | 
| 58 | 
            -
                     | 
| 65 | 
            +
                  class List < RunnerCLI
         | 
| 66 | 
            +
                    desc 'samples', 'Lists known code samples'
         | 
| 67 | 
            +
                    method_option :verbose, aliases: '-v', desc: 'Verbose: display more details'
         | 
| 68 | 
            +
                    method_option :cwd, desc: 'Working directory for detecting and running commands'
         | 
| 69 | 
            +
                    def samples
         | 
| 70 | 
            +
                      samples = runner.known_samples.map do |sample|
         | 
| 71 | 
            +
                        [set_color(sample.name, :bold), sample.source_file]
         | 
| 72 | 
            +
                      end
         | 
| 73 | 
            +
                      print_table samples
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    desc 'tasks', 'List known tasks'
         | 
| 77 | 
            +
                    method_option :verbose, aliases: '-v', desc: 'Verbose: display more details'
         | 
| 78 | 
            +
                    method_option :cwd, desc: 'Working directory for detecting and running commands'
         | 
| 79 | 
            +
                    def tasks
         | 
| 80 | 
            +
                      runner.known_tasks.map do |task|
         | 
| 81 | 
            +
                        task_id = set_color(task, :bold)
         | 
| 82 | 
            +
                        if options[:verbose]
         | 
| 83 | 
            +
                          details = runner.task_for(task)
         | 
| 84 | 
            +
                          details = details.call if details.respond_to? :call
         | 
| 85 | 
            +
                          details = "\n#{details}".lines.join('  ') if details.lines.size > 1
         | 
| 86 | 
            +
                          say "#{task_id}: #{details}"
         | 
| 87 | 
            +
                        else
         | 
| 88 | 
            +
                          say task_id
         | 
| 89 | 
            +
                        end
         | 
| 90 | 
            +
                      end
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  class Show < RunnerCLI
         | 
| 95 | 
            +
                    desc 'sample <name>', 'Show detailed information about a code sample'
         | 
| 96 | 
            +
                    method_option :verbose, aliases: '-v', desc: 'Verbose: display more details'
         | 
| 97 | 
            +
                    method_option :cwd, desc: 'Working directory for detecting and running commands'
         | 
| 98 | 
            +
                    def sample(sample_name)
         | 
| 99 | 
            +
                      sample = runner.find_sample(sample_name)
         | 
| 100 | 
            +
                      say sample.to_s(options[:verbose])
         | 
| 101 | 
            +
                    end
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  desc 'list', 'List known tasks or code samples'
         | 
| 105 | 
            +
                  subcommand 'list', List
         | 
| 106 | 
            +
                  desc 'show', 'Show details about a task or code sample'
         | 
| 107 | 
            +
                  subcommand 'show', Show
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  no_commands do
         | 
| 110 | 
            +
                    def show_sample(_sample_name)
         | 
| 111 | 
            +
                    end
         | 
| 59 112 | 
             
                  end
         | 
| 60 113 | 
             
                end
         | 
| 61 114 | 
             
              end
         | 
| 62 115 | 
             
            end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            # rubocop:enable Metrics/LineLength
         |