automateit 0.70923
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +1 -0
- data/CHANGES.txt +100 -0
- data/Hoe.rake +35 -0
- data/Manifest.txt +111 -0
- data/README.txt +44 -0
- data/Rakefile +284 -0
- data/TESTING.txt +57 -0
- data/TODO.txt +26 -0
- data/TUTORIAL.txt +390 -0
- data/bin/ai +3 -0
- data/bin/aifield +82 -0
- data/bin/aitag +128 -0
- data/bin/automateit +117 -0
- data/docs/friendly_errors.txt +50 -0
- data/docs/previews.txt +86 -0
- data/env.sh +4 -0
- data/examples/basic/Rakefile +26 -0
- data/examples/basic/config/automateit_env.rb +16 -0
- data/examples/basic/config/fields.yml +3 -0
- data/examples/basic/config/tags.yml +13 -0
- data/examples/basic/dist/README.txt +9 -0
- data/examples/basic/dist/myapp_server.erb +30 -0
- data/examples/basic/install.log +15 -0
- data/examples/basic/lib/README.txt +10 -0
- data/examples/basic/recipes/README.txt +4 -0
- data/examples/basic/recipes/install.rb +53 -0
- data/examples/basic/recipes/uninstall.rb +6 -0
- data/gpl.txt +674 -0
- data/lib/automateit.rb +66 -0
- data/lib/automateit/account_manager.rb +106 -0
- data/lib/automateit/account_manager/linux.rb +171 -0
- data/lib/automateit/account_manager/passwd.rb +69 -0
- data/lib/automateit/account_manager/portable.rb +136 -0
- data/lib/automateit/address_manager.rb +165 -0
- data/lib/automateit/address_manager/linux.rb +80 -0
- data/lib/automateit/address_manager/portable.rb +37 -0
- data/lib/automateit/cli.rb +80 -0
- data/lib/automateit/common.rb +65 -0
- data/lib/automateit/constants.rb +33 -0
- data/lib/automateit/edit_manager.rb +292 -0
- data/lib/automateit/error.rb +10 -0
- data/lib/automateit/field_manager.rb +103 -0
- data/lib/automateit/interpreter.rb +641 -0
- data/lib/automateit/package_manager.rb +242 -0
- data/lib/automateit/package_manager/apt.rb +63 -0
- data/lib/automateit/package_manager/egg.rb +64 -0
- data/lib/automateit/package_manager/gem.rb +179 -0
- data/lib/automateit/package_manager/portage.rb +69 -0
- data/lib/automateit/package_manager/yum.rb +65 -0
- data/lib/automateit/platform_manager.rb +47 -0
- data/lib/automateit/platform_manager/darwin.rb +30 -0
- data/lib/automateit/platform_manager/debian.rb +26 -0
- data/lib/automateit/platform_manager/freebsd.rb +25 -0
- data/lib/automateit/platform_manager/gentoo.rb +26 -0
- data/lib/automateit/platform_manager/lsb.rb +40 -0
- data/lib/automateit/platform_manager/struct.rb +78 -0
- data/lib/automateit/platform_manager/uname.rb +29 -0
- data/lib/automateit/platform_manager/windows.rb +33 -0
- data/lib/automateit/plugin.rb +7 -0
- data/lib/automateit/plugin/base.rb +32 -0
- data/lib/automateit/plugin/driver.rb +218 -0
- data/lib/automateit/plugin/manager.rb +232 -0
- data/lib/automateit/project.rb +460 -0
- data/lib/automateit/root.rb +14 -0
- data/lib/automateit/service_manager.rb +79 -0
- data/lib/automateit/service_manager/chkconfig.rb +39 -0
- data/lib/automateit/service_manager/rc_update.rb +37 -0
- data/lib/automateit/service_manager/sysv.rb +126 -0
- data/lib/automateit/service_manager/update_rcd.rb +35 -0
- data/lib/automateit/shell_manager.rb +261 -0
- data/lib/automateit/shell_manager/base_link.rb +67 -0
- data/lib/automateit/shell_manager/link.rb +24 -0
- data/lib/automateit/shell_manager/portable.rb +421 -0
- data/lib/automateit/shell_manager/symlink.rb +32 -0
- data/lib/automateit/shell_manager/which.rb +25 -0
- data/lib/automateit/tag_manager.rb +63 -0
- data/lib/automateit/tag_manager/struct.rb +101 -0
- data/lib/automateit/tag_manager/tag_parser.rb +91 -0
- data/lib/automateit/tag_manager/yaml.rb +29 -0
- data/lib/automateit/template_manager.rb +55 -0
- data/lib/automateit/template_manager/base.rb +172 -0
- data/lib/automateit/template_manager/erb.rb +17 -0
- data/lib/ext/metaclass.rb +17 -0
- data/lib/ext/object.rb +18 -0
- data/lib/hashcache.rb +22 -0
- data/lib/helpful_erb.rb +63 -0
- data/lib/nested_error.rb +33 -0
- data/lib/queued_logger.rb +68 -0
- data/lib/tempster.rb +239 -0
- data/misc/index_gem_repository.rb +303 -0
- data/misc/setup_egg.rb +12 -0
- data/misc/setup_gem_dependencies.sh +7 -0
- data/misc/setup_rubygems.sh +21 -0
- data/misc/which.cmd +6 -0
- data/spec/extras/automateit_service_sysv_test +50 -0
- data/spec/extras/scratch.rb +15 -0
- data/spec/extras/simple_recipe.rb +8 -0
- data/spec/integration/account_manager_spec.rb +218 -0
- data/spec/integration/address_manager_linux_spec.rb +119 -0
- data/spec/integration/address_manager_portable_spec.rb +30 -0
- data/spec/integration/cli_spec.rb +215 -0
- data/spec/integration/examples_spec.rb +54 -0
- data/spec/integration/examples_spec_editor.rb +71 -0
- data/spec/integration/package_manager_spec.rb +104 -0
- data/spec/integration/platform_manager_spec.rb +69 -0
- data/spec/integration/service_manager_sysv_spec.rb +115 -0
- data/spec/integration/shell_manager_spec.rb +471 -0
- data/spec/integration/template_manager_erb_spec.rb +31 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/unit/edit_manager_spec.rb +162 -0
- data/spec/unit/field_manager_spec.rb +79 -0
- data/spec/unit/hashcache_spec.rb +28 -0
- data/spec/unit/interpreter_spec.rb +98 -0
- data/spec/unit/platform_manager_spec.rb +44 -0
- data/spec/unit/plugins_spec.rb +253 -0
- data/spec/unit/tag_manager_spec.rb +189 -0
- data/spec/unit/template_manager_erb_spec.rb +137 -0
- metadata +249 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# == PlatformManager::Windows
|
2
|
+
#
|
3
|
+
# A PlatformManager driver for Windows systems.
|
4
|
+
class AutomateIt::PlatformManager::Windows < AutomateIt::PlatformManager::Struct
|
5
|
+
def available?
|
6
|
+
return RUBY_PLATFORM.match(/mswin/) ? true : false
|
7
|
+
end
|
8
|
+
|
9
|
+
def suitability(method, *args) # :nodoc:
|
10
|
+
# Must be higher than PlatformManager::Struct
|
11
|
+
return available? ? 3 : 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def _prepare
|
15
|
+
return if @struct[:release]
|
16
|
+
@struct[:os] = "windows"
|
17
|
+
@struct[:arch] = ENV["PROCESSOR_ARCHITECTURE"]
|
18
|
+
@struct[:distro] = "microsoft"
|
19
|
+
# VER values: http://www.ss64.com/nt/ver.html
|
20
|
+
@struct[:release] = `ver`.strip.match(/Windows (\w+)/)[1].downcase
|
21
|
+
@struct
|
22
|
+
end
|
23
|
+
private :_prepare
|
24
|
+
|
25
|
+
def query(search)
|
26
|
+
_prepare
|
27
|
+
super(search)
|
28
|
+
end
|
29
|
+
|
30
|
+
def single_vendor?
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module AutomateIt
|
2
|
+
class Plugin
|
3
|
+
# == Plugin::Base
|
4
|
+
#
|
5
|
+
# An AutomateIt Plugin provides the Interpreter with the functionality that
|
6
|
+
# users actually care about, such as installing packages or adding users.
|
7
|
+
# The Plugin::Base class isn't useful by itself but provides behavior
|
8
|
+
# that's inherited by the Plugin::Manager and Plugin::Driver classes.
|
9
|
+
class Base < Common
|
10
|
+
def setup(opts={}) # :nodoc:
|
11
|
+
super(opts)
|
12
|
+
@interpreter ||= opts[:interpreter] \
|
13
|
+
|| AutomateIt::Interpreter.new(:parent => self)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get token for the plugin. The token is a symbol that represents the
|
17
|
+
# classname of the underlying object.
|
18
|
+
#
|
19
|
+
# Example:
|
20
|
+
# AddressManager.token # => :address_manager
|
21
|
+
# AddressManager::Portable.token => :portable
|
22
|
+
def token
|
23
|
+
self.class.token
|
24
|
+
end
|
25
|
+
|
26
|
+
# See #token.
|
27
|
+
def self.token
|
28
|
+
return to_s.demodulize.underscore.to_sym
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
module AutomateIt
|
2
|
+
class Plugin
|
3
|
+
# == Plugin::Driver
|
4
|
+
#
|
5
|
+
# A driver provides the low-level functionality for features, e.g., the
|
6
|
+
# PackageManager::APT driver is responsible for installing a software
|
7
|
+
# package using the Debian <tt>apt-get</tt> command. Multiple drivers
|
8
|
+
# providing common functionality are managed by a single Manager class,
|
9
|
+
# e.g., drivers that install software packages are managed by the
|
10
|
+
# PackageManager.
|
11
|
+
#
|
12
|
+
# A driver may only be available on certain platforms and provides its
|
13
|
+
# manager with an idea of when it's suitable. For example, if a platform
|
14
|
+
# doesn't have the <tt>apt-get</tt> command, the PackageManager::APT driver
|
15
|
+
# must tell the PackageManager class that it's not suitable.
|
16
|
+
#
|
17
|
+
# === Writing your own drivers
|
18
|
+
#
|
19
|
+
# To write a driver, find the most similar driver available for a specific
|
20
|
+
# plugin, copy it, and rework its code. Save the code for the new driver in
|
21
|
+
# a file ending with .rb into the project’s +lib+ directory, it will be
|
22
|
+
# automatically loaded whenever the Interpreter for that project is run.
|
23
|
+
# Please test and contribute drivers so that others can benefit.
|
24
|
+
#
|
25
|
+
# IMPORTANT GOTCHA: You must prefix the AutomateIt module name with a "::"!
|
26
|
+
#
|
27
|
+
# Here's a minimalistic PackageManager that can be dropped into +lib+:
|
28
|
+
#
|
29
|
+
# class MyDriver < ::AutomateIt::PackageManager::BaseDriver
|
30
|
+
# depends_on :nothing
|
31
|
+
#
|
32
|
+
# def suitability(method, *args) # :nodoc:
|
33
|
+
# # Never select as default driver
|
34
|
+
# return 0
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
class Driver < Base
|
38
|
+
# Driver classes. Represented as a hash of manager tokens to arrays of
|
39
|
+
# their driver classes, for example:
|
40
|
+
#
|
41
|
+
# { :package_manager => [
|
42
|
+
# AutomateIt::PackageManager::APT,
|
43
|
+
# AutomateIt::PackageManager::YUM,
|
44
|
+
# ...
|
45
|
+
cattr_accessor :classes
|
46
|
+
self.classes = {}
|
47
|
+
|
48
|
+
# Retrieve the manager token for this driver
|
49
|
+
def self.manager_token
|
50
|
+
fragments = base_driver.to_s.split(/::/)
|
51
|
+
return fragments[fragments.size-2].underscore.to_sym
|
52
|
+
end
|
53
|
+
|
54
|
+
BASE_DRIVER_NAME = "BaseDriver"
|
55
|
+
|
56
|
+
# Is this a base driver?
|
57
|
+
def self.base_driver?
|
58
|
+
to_s =~ /::#{BASE_DRIVER_NAME}/
|
59
|
+
end
|
60
|
+
|
61
|
+
# Retrieve the base driver class for this driver.
|
62
|
+
def self.base_driver
|
63
|
+
ancestors.select{|t| t.to_s =~ /::#{BASE_DRIVER_NAME}/}.last
|
64
|
+
end
|
65
|
+
|
66
|
+
# Register drivers. Concrete drivers are added to a class-wide data
|
67
|
+
# structure which maps them to the manager they belong to.
|
68
|
+
def self.inherited(subclass) # :nodoc:
|
69
|
+
base_driver = subclass.base_driver
|
70
|
+
if subclass.base_driver?
|
71
|
+
# Ignore, base drivers should never be registered
|
72
|
+
elsif base_driver
|
73
|
+
manager = subclass.manager_token
|
74
|
+
classes[manager] ||= []
|
75
|
+
classes[manager] << subclass unless classes[manager].include?(subclass)
|
76
|
+
### puts "manager %s / driver %s" % [manager, subclass]
|
77
|
+
else
|
78
|
+
# XXX Should this really raise an exception?
|
79
|
+
raise TypeError.new("Can't determine manager for driver: #{subclass}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Declare that this driver class is abstract. It can be subclassed but
|
84
|
+
# will not be instantiated by the Interpreter's Managers. BaseDriver
|
85
|
+
# classes are automatically declared abstract.
|
86
|
+
def self.abstract_driver
|
87
|
+
if base_driver?
|
88
|
+
# Ignore, base drivers should never have been registered
|
89
|
+
elsif manager = manager_token
|
90
|
+
classes[manager].delete(self)
|
91
|
+
else
|
92
|
+
raise TypeError.new("Can't find manager for abstract plugin: #{self}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Defines resources this driver depends on.
|
97
|
+
#
|
98
|
+
# Options:
|
99
|
+
# * :files -- Array of filenames that must exist.
|
100
|
+
# * :directories -- Array of directories that must exist.
|
101
|
+
# * :programs -- Array of programs, checked with +which+, that must exist.
|
102
|
+
# * :callbacks -- Array of lambdas that must return true.
|
103
|
+
# * :libraries -- Array of libraries to +require+.
|
104
|
+
#
|
105
|
+
# Example:
|
106
|
+
# class APT < Plugin::Driver
|
107
|
+
# depends_on :programs => %w(apt-get dpkg)
|
108
|
+
# # ...
|
109
|
+
# end
|
110
|
+
def self.depends_on(opts)
|
111
|
+
meta_eval do
|
112
|
+
attr_accessor :_depends_on_opts, :_is_available, :_missing_dependencies
|
113
|
+
end
|
114
|
+
self._depends_on_opts = opts
|
115
|
+
end
|
116
|
+
|
117
|
+
# Is this driver available on this platform? Queries the dependencies set
|
118
|
+
# by #depends_on to make sure that they're all present, otherwise raises
|
119
|
+
# a NotImplementedError. If a driver author needs to do some other kind
|
120
|
+
# of check, it's reasonable to override this method.
|
121
|
+
#
|
122
|
+
# For example, this method is used by the PackageManager driver for APT
|
123
|
+
# to determine if the "apt-get" program is installed.
|
124
|
+
#
|
125
|
+
# What's the difference between #available? and #suitability? The
|
126
|
+
# #available? method is used to determine if the driver *can* do the
|
127
|
+
# work, while the #suitability method determines if the driver *should*
|
128
|
+
# be automatically selected.
|
129
|
+
def available?
|
130
|
+
# Some drivers don't run +depends_on+, so assume they're available.
|
131
|
+
return true unless self.class.respond_to?(:_depends_on_opts)
|
132
|
+
opts = self.class._depends_on_opts
|
133
|
+
|
134
|
+
# Driver said that it +depends_on :nothing+, so it's available.
|
135
|
+
return true if opts == :nothing
|
136
|
+
|
137
|
+
is_available = self.class._is_available
|
138
|
+
if is_available.nil? and opts.nil?
|
139
|
+
#log.debug(PNOTE+"don't know if driver #{self.class} is available, maybe it doesn't state what it +depends_on+")
|
140
|
+
return false
|
141
|
+
elsif is_available.nil?
|
142
|
+
all_present = true
|
143
|
+
missing = {}
|
144
|
+
for kind in opts.keys
|
145
|
+
#IK# for kind in [:files, :directories, :programs, :callbacks, :requires, :libraries]
|
146
|
+
next unless opts[kind]
|
147
|
+
for item in [opts[kind]].flatten
|
148
|
+
present = \
|
149
|
+
case kind
|
150
|
+
when :files
|
151
|
+
File.exists?(item)
|
152
|
+
when :directories
|
153
|
+
File.directory?(item)
|
154
|
+
when :programs
|
155
|
+
# XXX Find less awkward way to check if a program exists. Can't use +shell_manager.which+ because that will use +dispatch+ and go into an infinite loop checking +available?+. The +which+ command isn't available on all platforms, so that failure must be handled as well.
|
156
|
+
begin
|
157
|
+
interpreter.shell_manager[:which].which!(item)
|
158
|
+
true
|
159
|
+
rescue ArgumentError, NotImplementedError, NoMethodError
|
160
|
+
false
|
161
|
+
end
|
162
|
+
when :requires, :libraries
|
163
|
+
begin
|
164
|
+
require item
|
165
|
+
true
|
166
|
+
rescue LoadError
|
167
|
+
false
|
168
|
+
end
|
169
|
+
when :callbacks
|
170
|
+
item.call() ? true : false
|
171
|
+
else
|
172
|
+
raise "unknown kind: #{kind}"
|
173
|
+
end
|
174
|
+
unless present
|
175
|
+
all_present = false
|
176
|
+
missing[kind] ||= []
|
177
|
+
missing[kind] << item
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
self.class._missing_dependencies = missing
|
182
|
+
self.class._is_available = all_present
|
183
|
+
log.debug(PNOTE+"Driver #{self.class} #{all_present ? "is" : "isn't"} available")
|
184
|
+
end
|
185
|
+
return self.class._is_available
|
186
|
+
end
|
187
|
+
|
188
|
+
# Raise a NotImplementedError if this driver is called but is not
|
189
|
+
# #available?.
|
190
|
+
def _raise_unless_available
|
191
|
+
unless available?
|
192
|
+
msg = ""
|
193
|
+
for kind, elements in self.class._missing_dependencies
|
194
|
+
msg << "; " unless msg == ""
|
195
|
+
msg << "%s: %s" % [kind, elements.sort.join(', ')]
|
196
|
+
end
|
197
|
+
raise NotImplementedError.new("Missing dependencies -- %s" % msg)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# What is this driver's suitability for automatic detection? The Manager
|
202
|
+
# queries its drivers when there isn't a driver specified with a
|
203
|
+
# <tt>:with</tt> or #default so it can choose a suitable driver for the
|
204
|
+
# +method+ and +args+. Any driver that returns an integer 1 or greater
|
205
|
+
# claims to be suitable. The Manager will then select the driver with the
|
206
|
+
# highest suitability level. Drivers that return an integer less than 1
|
207
|
+
# are excluded from automatic detection.
|
208
|
+
#
|
209
|
+
# The <tt>available?</tt> method is used to determine if the driver can
|
210
|
+
# do the work, while the <tt>suitability</tt> method determines if the
|
211
|
+
# driver should be automatically selected.
|
212
|
+
def suitability(method, *args, &block)
|
213
|
+
log.debug("driver #{self.class} doesn't implement the +suitability+ method")
|
214
|
+
return -1
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module AutomateIt
|
2
|
+
class Plugin
|
3
|
+
# == Plugin::Manager
|
4
|
+
#
|
5
|
+
# A manager provides high-level wrappers for features, e.g.
|
6
|
+
# installing software packages. It does not actually implement these
|
7
|
+
# features, but instead manages a set of drivers. When one of the manager's
|
8
|
+
# wrapper methods is called, it queries the drivers to find the most
|
9
|
+
# suitable one and dispatches the user's request to that driver.
|
10
|
+
#
|
11
|
+
# For example, the PlatformManager is a Manager class that manages a set of
|
12
|
+
# Driver instances, including PlatformManager::Uname and
|
13
|
+
# PlatformManager::LSB. When you invoke the high-level
|
14
|
+
# PlatformManager#query wrapper method, it interrogates the drivers to find
|
15
|
+
# which one is best-suited for this method and then dispatches the request
|
16
|
+
# to that driver's low-level implementation of this method.
|
17
|
+
#
|
18
|
+
# The manager subclasses typically have no functionality of their own and
|
19
|
+
# just contain wrapper methods and documentation.
|
20
|
+
class Manager < Base
|
21
|
+
# Array of all managers.
|
22
|
+
cattr_accessor :classes
|
23
|
+
self.classes = []
|
24
|
+
|
25
|
+
# Register managers.
|
26
|
+
def self.inherited(subclass) # :nodoc:
|
27
|
+
classes << subclass unless classes.include?(subclass)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Declare that this manager class is abstract. It can be subclassed but
|
31
|
+
# will not be instantiated by the Interpreter.
|
32
|
+
def self.abstract_manager
|
33
|
+
classes.delete(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
# List of aliased methods for this manager, populated by ::alias_methods.
|
37
|
+
class_inheritable_accessor :aliased_methods
|
38
|
+
|
39
|
+
# Methods to alias into the Interpreter, specified as an array of symbols.
|
40
|
+
def self.alias_methods(*methods)
|
41
|
+
if methods.empty?
|
42
|
+
self.aliased_methods
|
43
|
+
else
|
44
|
+
self.aliased_methods ||= Set.new
|
45
|
+
self.aliased_methods.merge(methods.flatten)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Drivers for this manager as a hash of driver tokens to driver
|
50
|
+
# instances.
|
51
|
+
attr_accessor :drivers
|
52
|
+
|
53
|
+
# Driver classes used by this Manager.
|
54
|
+
def self.driver_classes
|
55
|
+
Driver.classes[token] || []
|
56
|
+
end
|
57
|
+
|
58
|
+
# Options:
|
59
|
+
# * :default -- The token of the driver to set as the default.
|
60
|
+
def setup(opts={})
|
61
|
+
super(opts)
|
62
|
+
|
63
|
+
@default ||= nil
|
64
|
+
|
65
|
+
instantiate_drivers
|
66
|
+
|
67
|
+
if driver = opts[:default] || opts[:driver]
|
68
|
+
default(opts[:default]) if opts[:default]
|
69
|
+
@drivers[driver].setup(opts)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Instantiate drivers for this manager. This method is smart enough that
|
74
|
+
# it can be called multiple times and will only instantiate drivers it
|
75
|
+
# hasn't instantiated yet. All drivers will share an instance of the
|
76
|
+
# Interpreter, thus providing common state storage.
|
77
|
+
def instantiate_drivers
|
78
|
+
@drivers ||= {}
|
79
|
+
self.class.driver_classes.each do |driver_class|
|
80
|
+
driver_token = driver_class.token
|
81
|
+
unless @drivers[driver_token]
|
82
|
+
@drivers[driver_token] = driver_class.allocate
|
83
|
+
end
|
84
|
+
end
|
85
|
+
self.class.driver_classes.each do |driver_class|
|
86
|
+
driver_token = driver_class.token
|
87
|
+
@drivers[driver_token].setup(:interpreter => @interpreter)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the Driver with the specified token. E.g., +:apt+ will return
|
92
|
+
# the +APT+ driver.
|
93
|
+
def [](token)
|
94
|
+
return @drivers[token]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Manipulate the default driver. Without arguments, gets the driver token
|
98
|
+
# as a symbol. With argument, sets the default driver to the +token+,
|
99
|
+
# e.g., the argument <tt>:apt</tt> will make the +APT+ driver the default.
|
100
|
+
def default(token=nil)
|
101
|
+
if token.nil?
|
102
|
+
@default
|
103
|
+
else
|
104
|
+
@default = token
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Set the default driver to the +token+. See also #default.
|
109
|
+
def default=(token)
|
110
|
+
default(token)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Dispatch a method by guessing its name. This is the recommended way to
|
114
|
+
# write wrappers for a Manager methods.
|
115
|
+
#
|
116
|
+
# Example:
|
117
|
+
# class MyManager < Plugin::Manager
|
118
|
+
# # Your RDoc here
|
119
|
+
# def my_method(*args)
|
120
|
+
# # Will guess that you want to +dispatch_to+ the +my_method+ by
|
121
|
+
# # introspecting the name of the wrapper method.
|
122
|
+
# dispatch(*args)
|
123
|
+
# end
|
124
|
+
# ...
|
125
|
+
# end
|
126
|
+
def dispatch(*args, &block)
|
127
|
+
# Extract caller's method as symbol to save user from having to specify it
|
128
|
+
method = caller[0].match(/:in `(.+?)'/)[1].to_sym
|
129
|
+
dispatch_to(method, *args, &block)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Dispatch the +method+ with +args+ and +block+ to the appropriate
|
133
|
+
# driver. If the arguments include an option of the form <tt>:with =>
|
134
|
+
# :mytoken</tt> argument, then the method will be dispatched to the
|
135
|
+
# driver represented by <tt>:mytoken</tt>. If no :with argument is
|
136
|
+
# specified, the most-suitable driver will be automatically selected. If
|
137
|
+
# no suitable driver is available, a NotImplementedError will be raised.
|
138
|
+
#
|
139
|
+
# Examples:
|
140
|
+
# # Run 'hostnames' method on most appropriate AddressManager driver:
|
141
|
+
# address_manager.dispatch_to(:hostnames)
|
142
|
+
#
|
143
|
+
# # Run AddressManager::Portable#hostnames
|
144
|
+
# address_manager.dispatch_to(:hostnames, :with => :portable)
|
145
|
+
#
|
146
|
+
# You will typically not want to use this method directly and instead
|
147
|
+
# write wrappers that call #dispatch because it can guess the name of
|
148
|
+
# the +method+ argument for you.
|
149
|
+
def dispatch_to(method, *args, &block)
|
150
|
+
list, options = args_and_opts(*args)
|
151
|
+
driver = \
|
152
|
+
if options and options[:with]
|
153
|
+
@drivers[options[:with].to_sym]
|
154
|
+
elsif default
|
155
|
+
@drivers[default.to_sym]
|
156
|
+
else
|
157
|
+
driver_for(method, *args, &block)
|
158
|
+
end
|
159
|
+
driver.send(method, *args, &block)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Same as #dispatch_to but returns nil if couldn't dispatch, rather than
|
163
|
+
# raising an exception.
|
164
|
+
def dispatch_safely_to(method, *args, &block)
|
165
|
+
begin
|
166
|
+
dispatch_to(method, *args, &block)
|
167
|
+
rescue NotImplementedError
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Same as #dispatch but returns nil if couldn't dispatch, rather than
|
173
|
+
# raising an exception.
|
174
|
+
def dispatch_safely(*args, &block)
|
175
|
+
method = caller[0].match(/:in `(.+?)'/)[1].to_sym
|
176
|
+
dispatch_safely_to(method, *args, &block)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns structure which helps choose a suitable driver for the +method+
|
180
|
+
# and +args+. Result is a hash of driver tokens and their suitability
|
181
|
+
# levels.
|
182
|
+
#
|
183
|
+
# For example, if we ask the AddressManager for suitability levels for
|
184
|
+
# the AddressManager#hostnames method, we might find that there are two
|
185
|
+
# drivers (:portable is the token for AddressManager::Portable) and that
|
186
|
+
# the :linux driver is most appropriate because it has the highest
|
187
|
+
# suitability level:
|
188
|
+
#
|
189
|
+
# address_manager.driver_suitability_levels_for(:hostnames)
|
190
|
+
# # => {:portable=>1, :linux=>2}
|
191
|
+
def driver_suitability_levels_for(method, *args, &block)
|
192
|
+
results = {}
|
193
|
+
@drivers.each_pair do |name, driver|
|
194
|
+
next unless driver.respond_to?(method)
|
195
|
+
results[name] = driver.suitability(method, *args, &block)
|
196
|
+
end
|
197
|
+
return results
|
198
|
+
end
|
199
|
+
|
200
|
+
# Get the most suitable driver for this +method+ and +args+. Uses
|
201
|
+
# automatic-detection routines and returns the most suitable driver
|
202
|
+
# instance found, else raises a NotImplementedError if no suitable driver
|
203
|
+
# is found.
|
204
|
+
def driver_for(method, *args, &block)
|
205
|
+
begin
|
206
|
+
driver, level = driver_suitability_levels_for(method, *args, &block).sort_by{|k,v| v}.last
|
207
|
+
rescue IndexError
|
208
|
+
driver = nil
|
209
|
+
level = -1
|
210
|
+
end
|
211
|
+
if driver and level > 0
|
212
|
+
return @drivers[driver]
|
213
|
+
else
|
214
|
+
raise NotImplementedError.new("can't find driver for method '#{method}' with arguments: #{args.inspect}")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Is a driver available for this +method+ and +args+? Uses
|
219
|
+
# automatic-detection routines and returns a boolean to indicate if a
|
220
|
+
# suitable driver is available. Unlike #driver_for, this will not raise
|
221
|
+
# an exception.
|
222
|
+
def available?(method, *args, &block)
|
223
|
+
begin
|
224
|
+
driver_for(method, *args, &block)
|
225
|
+
true
|
226
|
+
rescue NotImplementedError
|
227
|
+
false
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|