Class AutomateIt::Plugin::Driver
In: lib/automateit/plugin/driver.rb
Parent: Base

Plugin::Driver

A driver provides the low-level functionality for features, e.g., the PackageManager::APT driver is responsible for installing a software package using the Debian apt-get command. Multiple drivers providing common functionality are managed by a single Manager class, e.g., drivers that install software packages are managed by the PackageManager.

A driver may only be available on certain platforms and provides its manager with an idea of when it‘s suitable. For example, if a platform doesn‘t have the apt-get command, the PackageManager::APT driver must tell the PackageManager class that it‘s not suitable.

Writing your own drivers

To write a driver, find the most similar driver available for a specific plugin, copy it, and rework its code. Save the code for the new driver in a file ending with .rb into the project’s lib directory, it will be automatically loaded whenever the Interpreter for that project is run. Please test and contribute drivers so that others can benefit.

IMPORTANT GOTCHA: You must prefix the AutomateIt module name with a "::"!

Here‘s a minimalistic PackageManager that can be dropped into lib:

  class MyDriver < ::AutomateIt::PackageManager::BaseDriver
    depends_on :nothing

    def suitability(method, *args) # :nodoc:
      # Never select as default driver
      return 0
    end
  end

Methods

Constants

BASE_DRIVER_NAME = "BaseDriver"

Attributes

manager  [RW]  Returns the Plugin::Manager instance for this Driver.

Public Class methods

Declare that this driver class is abstract. It can be subclassed but will not be instantiated by the Interpreter‘s Managers. BaseDriver classes are automatically declared abstract.

[Source]

# File lib/automateit/plugin/driver.rb, line 102
      def self.abstract_driver
        if base_driver?
          # Ignore, base drivers should never have been registered
        elsif manager = manager_token
          classes[manager].delete(self)
        else
          raise TypeError.new("Can't find manager for abstract plugin: #{self}")
        end
      end

Retrieve the base driver class for this driver.

[Source]

# File lib/automateit/plugin/driver.rb, line 74
      def self.base_driver
        ancestors.select{|t| t.to_s =~ /::#{BASE_DRIVER_NAME}/}.last
      end

Is this a base driver?

[Source]

# File lib/automateit/plugin/driver.rb, line 69
      def self.base_driver?
        to_s =~ /::#{BASE_DRIVER_NAME}/
      end

Defines resources this driver depends on.

Options:

  • :files — Array of filenames that must exist.
  • :directories — Array of directories that must exist.
  • :programs — Array of programs, checked with which, that must exist.
  • :callbacks — Array of lambdas that must return true.
  • :libraries — Array of libraries to require.

Example:

  class APT < Plugin::Driver
    depends_on :programs => %w(apt-get dpkg)
    # ...
 end

[Source]

# File lib/automateit/plugin/driver.rb, line 126
      def self.depends_on(opts)
        meta_eval do
          attr_accessor :_depends_on_opts, :_is_available, :_missing_dependencies
        end
        self._depends_on_opts = opts
      end

Retrieve the manager token for this driver

[Source]

# File lib/automateit/plugin/driver.rb, line 61
      def self.manager_token
        fragments = base_driver.to_s.split(/::/)
        return fragments[fragments.size-2].underscore.to_sym
      end

Public Instance methods

Is this driver available on this platform? Queries the dependencies set by depends_on to make sure that they‘re all present, otherwise raises a NotImplementedError. If a driver author needs to do some other kind of check, it‘s reasonable to override this method.

For example, this method is used by the PackageManager driver for APT to determine if the "apt-get" program is installed.

What‘s the difference between available? and suitability? The available? method is used to determine if the driver can do the work, while the suitability method determines if the driver should be automatically selected.

[Source]

# File lib/automateit/plugin/driver.rb, line 145
      def available?
        # Some drivers don't run +depends_on+, so assume they're available.
        return true unless self.class.respond_to?(:_depends_on_opts)
        opts = self.class._depends_on_opts

        # Driver said that it +depends_on :nothing+, so it's available.
        return true if opts == :nothing

        is_available = self.class._is_available
        if is_available.nil? and opts.nil?
          #log.debug(PNOTE+"don't know if driver #{self.class} is available, maybe it doesn't state what it +depends_on+")
          return false
        elsif is_available.nil?
          all_present = true
          missing = {}

          # Check callbacks last
          kinds = opts.keys
          callbacks = kinds.delete(:callbacks)
          kinds << callbacks if callbacks

          begin
            for kind in kinds
              next unless opts[kind]
              for item in [opts[kind]].flatten
                present = \
                  case kind
                  when :files
                    File.exists?(item)
                  when :directories
                    File.directory?(item)
                  when :programs
                    # TODO Driver#available? - Find better way to locate the platform's #which driver and use it to check if a program exists. This is tricky because this #available? method is the one that handles detection, yet we have to bypass it.
                    result = nil
                    for variant in %w(unix windows)
                      variant_token = "which_#{variant}".to_sym
                      begin
                        driver = interpreter.shell_manager[variant_token]
                        result = driver.which!(item)
                        ### puts "%s : %s for %s" % [variant, result, item]
                        break
                      rescue ArgumentError, NotImplementedError, NoMethodError => e
                        # Exceptions are expected, only print for debugging
                        ### puts e.inspect
                      end
                    end
                    result
                  when :requires, :libraries
                    begin
                      require item
                      true
                    rescue LoadError
                      false
                    end
                  when :callbacks
                    item.call() ? true : false
                  else
                    raise TypeError.new("Unknown kind: #{kind}")
                  end
                unless present
                  all_present = false
                  missing[kind] ||= []
                  missing[kind] << item

                  # Do not continue scanning if dependency is missed
                  raise EOFError.new("break")
                end
              end
            end
          rescue EOFError => e
            # Ignore expected "break" warning
            raise e unless e.message == "break"
          end
          self.class._missing_dependencies = missing
          self.class._is_available = all_present
          log.debug(PNOTE+"Driver #{self.class} #{all_present ? "is" : "isn't"} available")
        end
        return self.class._is_available
      end

Setup a Driver.

Options:

[Source]

# File lib/automateit/plugin/driver.rb, line 55
      def setup(opts={})
        self.manager = opts[:manager] if opts[:manager]
        super(opts)
      end

What is this driver‘s suitability for automatic detection? The Manager queries its drivers when there isn‘t a driver specified with a :with or default so it can choose a suitable driver for the method and args. Any driver that returns an integer 1 or greater claims to be suitable. The Manager will then select the driver with the highest suitability level. Drivers that return an integer less than 1 are excluded from automatic detection.

The available? method is used to determine if the driver can do the work, while the suitability method determines if the driver should be automatically selected.

[Source]

# File lib/automateit/plugin/driver.rb, line 250
      def suitability(method, *args, &block)
        log.debug("driver #{self.class} doesn't implement the +suitability+ method")
        return -1
      end

Protected Instance methods

Raise a NotImplementedError if this driver is called but is not available?.

[Source]

# File lib/automateit/plugin/driver.rb, line 227
      def _raise_unless_available
        unless available?
          msg = ""
          for kind, elements in self.class._missing_dependencies
            msg << "; " unless msg == ""
            msg << "%s: %s" % [kind, elements.sort.join(', ')]
          end
          raise NotImplementedError.new("Missing dependencies -- %s" % msg)
        end
      end

[Validate]