NeverBounce/NeverBounceCli-Ruby

View on GitHub
lib/never_bounce/cli/script/base.rb

Summary

Maintainability
A
0 mins
Test Coverage

require "shellwords"

require "never_bounce/cli/feature/basic_initialize"
require "never_bounce/cli/feature/require_attr"

module NeverBounce; module CLI; module Script
  # Barebones script base class.
  # @abstract
  # @see CLI::Feature::BasicInitialize
  # @see CLI::Feature::RequireAttr
  class Base
    CLI::Feature::BasicInitialize.load(self)
    CLI::Feature::RequireAttr.load(self)

    attr_writer :argv, :env, :stderr, :stdout

    # Command-line arguments. Default is <tt>ARGV</tt>.
    # @return [Array]
    def argv
      @argv ||= ARGV
    end

    # A *copy* of the environment for value-reading purposes. Default is <tt>ENV.to_h</tt>.
    # @!attribute env
    # @return [Hash]
    def env
      # Ruby's `ENV` is a weird thing.
      # It's a direct `Object` which acts like `Hash`.
      # It can't be reliably dup'd cloned, at the same time writes to it are invocation-global.
      # This implicit read/write nature of `ENV` is a major hassle in tests, since it creates unnecessary side effects we have to tackle with specifically.
      # Solution for now:
      #
      # 1. Since 99% of the time our script isn't interested in *writing* to ENV, this method deals with the READ case as the most widely used one.
      # 2. If we ever need to write to ENV in order to *create* the environment for a child process or something, we'll find a way to do it with grace.
      #
      # Everything above is a comment to `.to_h`, mysteriously present on the next line.
      @env ||= ENV.to_h
    end

    # Script's error stream. Default is <tt>STDERR</tt>.
    # @return [IO]
    def stderr
      @stderr ||= STDERR
    end

    # Script's output stream. Default is <tt>STDOUT</tt>.
    # @return [IO]
    def stdout
      @stdout ||= STDOUT
    end

    # <tt>true</tt> if the script should be verbose.
    # @return [true]
    def verbose?
      true
    end

    #--------------------------------------- Service

    # @see #env_truthy?
    def env_falsey?(k)
      !env_truthy?(k)
    end

    # Return <tt>true</tt> if environment variable <tt>k</tt> is truthy.
    #
    #   env_truthy? "WITH_HTTP"   # => `true` or `false`
    #   env_truthy? :WITH_HTTP    # same as above
    def env_truthy?(k)
      self.class.env_value_truthy?(env[k.to_s])
    end

    # Return <tt>true</tt> if environment variable value is truthy.
    #
    #   # These are truthy.
    #   DEBUG=1
    #   DEBUG=true
    #   DEBUG=y
    #   DEBUG=yes
    def self.env_value_truthy?(s)
      ["1", "true", "y", "yes"].include? s.to_s.downcase
    end

    # Run system command, print it if verbose.
    # @return [mixed] Result of <tt>Kernel.system</tt>.
    def system(cmd, *args)
      puts "### #{cmd} #{args.map(&:shellescape).join(' ')}" if verbose?
      Kernel.system(cmd, *args)
    end

    #---------------------------------------

    # Main routine.
    # @abstract
    # @return [Integer]
    def main
      raise NotImplementedError, "Redefine `main` in your class: #{self.class}"
    end
  end
end; end; end