DannyBen/rigit

View on GitHub
lib/rigit/commands/build.rb

Summary

Maintainability
A
25 mins
Test Coverage
module Rigit::Commands
  # The {Build} module provides the {#build} command for the {CommandLine}
  # module.
  module Build
    # The command line +build+ command.
    def build
      BuildHandler.new(args).execute
    end

    # Internal class to handle scaffolding for the {CommandLine} class.
    class BuildHandler
      attr_reader :args, :rig_name, :target_dir, :force
      attr_accessor :overwrite_all, :skip_all

      include Colsole

      def initialize(args)
        @args = args
        @rig_name = args['RIG']
        @force = args['--force'] || (config.has_key?(:force) && config.force)
        @target_dir = '.'
      end

      def execute
        say "Building g`#{rig_name}`"
        say config.intro if config.has_key? :intro
        verify_dirs
        arguments = prompt.get_input params

        scaffold arguments

        say config.has_key?(:outro) ? config.outro : 'Done'
      end

    private

      # Call Rig#scaffold while checking each file to see if it should be
      # overwritten or not.
      def scaffold(arguments)
        execute_actions config.before, arguments if config.has_key? :before

        rig.scaffold arguments: arguments, target_dir: target_dir do |file|
          overwrite_file? file
        end

        execute_actions config.after, arguments if config.has_key? :after
      end

      # Execute user-defined system commands.
      # Actions are expected to be provided as a hash (label=command) and
      # both labels and commands accept string interpolation +%{tokens}+
      def execute_actions(actions, arguments)
        actions.each do |label, command|
          say "g`#{label}`" % arguments
          system command % arguments
        end
      end

      # Check various scenarios to decide if the file should be overwritten
      # or not. These are the scenarios covered by this method:
      # 1. The user provided +--focce+ in the command line
      # 2. The user answered "overwrite all" or "skip all" when he asked
      #    about the first conflicting file.
      # 3. In cases where an additive dir contains a file that was originally
      #    new, approved or rejected - we use this existing knowledge (which
      #    is stored in +response_log+.
      def overwrite_file?(file)
        return response_log[file] if response_log.has_key? file

        response_log[file] = true

        unless overwrite_all || force
          if skip_all
            response_log[file] = false
          elsif File.exist? file
            response_log[file] = prompt_user_to_overwrite file
          end
        end

        response_log[file]
      end

      def prompt_user_to_overwrite(file)
        say "File g`#{file}` already exists."
        tty_prompt.expand '  Overwrite?' do |menu|
          menu.choice key: 'y', name: 'overwrite',     value: true
          menu.choice key: 'n', name: 'skip',          value: false
          menu.choice key: 'a', name: 'overwrite all'  do
            @overwrite_all = true
            true
          end
          menu.choice key: 's', name: 'skip all' do
            @skip_all = true
            false
          end
        end
      end

      def response_log
        @response_log ||= {}
      end

      def rig
        @rig ||= Rigit::Rig.new rig_name
      end

      def config
        @config ||= rig.config
      end

      def params
        @params ||= params!
      end

      def params!
        output = {}
        input = args['PARAMS']
        input.each do |param|
          key, value = param.split '='
          output[key.to_sym] = value
        end
        output
      end

      def prompt
        @prompt ||= Rigit::Prompt.new config.params
      end

      def tty_prompt
        @tty_prompt ||= TTY::Prompt.new
      end

      def verify_dirs
        verify_source_dir
        verify_target_dir
      end

      def verify_source_dir
        raise Rigit::Exit, "No such rig: #{rig_name}" unless rig.exist?
      end

      def verify_target_dir
        return if Dir.empty?(target_dir) || force

        dirstring = target_dir == '.' ? 'Current directory' : "Directory '#{target_dir}'"
        options = { 'Abort' => :abort, 'Continue here' => :continue, 'Continue in a sub directory' => :create }
        response = tty_prompt.select "#{dirstring} is not empty.", options, symbols: { marker: '>' }

        case response
        when :abort
          raise Rigit::Exit, 'Goodbye'

        when :create
          create_subdir
          verify_target_dir
        end
      end

      def create_subdir
        folder = tty_prompt.ask 'Sub directory to create:', default: 'app'
        @target_dir = "#{target_dir}/#{folder}"
        Dir.mkdir target_dir unless Dir.exist? target_dir
        say "Creating in #{target_dir}"
      end
    end
  end
end