Class | AutomateIt::Plugin::Driver |
In: |
lib/automateit/plugin/driver.rb
|
Parent: | Base |
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.
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
BASE_DRIVER_NAME | = | "BaseDriver" |
manager | [RW] | Returns the Plugin::Manager instance for this Driver. |
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.
# 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.
# 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?
# 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:
Example:
class APT < Plugin::Driver depends_on :programs => %w(apt-get dpkg) # ... end
# 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
# 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
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.
# 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:
# 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.
# 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
Raise a NotImplementedError if this driver is called but is not available?.
# 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