automateit 0.70923

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. data.tar.gz.sig +1 -0
  2. data/CHANGES.txt +100 -0
  3. data/Hoe.rake +35 -0
  4. data/Manifest.txt +111 -0
  5. data/README.txt +44 -0
  6. data/Rakefile +284 -0
  7. data/TESTING.txt +57 -0
  8. data/TODO.txt +26 -0
  9. data/TUTORIAL.txt +390 -0
  10. data/bin/ai +3 -0
  11. data/bin/aifield +82 -0
  12. data/bin/aitag +128 -0
  13. data/bin/automateit +117 -0
  14. data/docs/friendly_errors.txt +50 -0
  15. data/docs/previews.txt +86 -0
  16. data/env.sh +4 -0
  17. data/examples/basic/Rakefile +26 -0
  18. data/examples/basic/config/automateit_env.rb +16 -0
  19. data/examples/basic/config/fields.yml +3 -0
  20. data/examples/basic/config/tags.yml +13 -0
  21. data/examples/basic/dist/README.txt +9 -0
  22. data/examples/basic/dist/myapp_server.erb +30 -0
  23. data/examples/basic/install.log +15 -0
  24. data/examples/basic/lib/README.txt +10 -0
  25. data/examples/basic/recipes/README.txt +4 -0
  26. data/examples/basic/recipes/install.rb +53 -0
  27. data/examples/basic/recipes/uninstall.rb +6 -0
  28. data/gpl.txt +674 -0
  29. data/lib/automateit.rb +66 -0
  30. data/lib/automateit/account_manager.rb +106 -0
  31. data/lib/automateit/account_manager/linux.rb +171 -0
  32. data/lib/automateit/account_manager/passwd.rb +69 -0
  33. data/lib/automateit/account_manager/portable.rb +136 -0
  34. data/lib/automateit/address_manager.rb +165 -0
  35. data/lib/automateit/address_manager/linux.rb +80 -0
  36. data/lib/automateit/address_manager/portable.rb +37 -0
  37. data/lib/automateit/cli.rb +80 -0
  38. data/lib/automateit/common.rb +65 -0
  39. data/lib/automateit/constants.rb +33 -0
  40. data/lib/automateit/edit_manager.rb +292 -0
  41. data/lib/automateit/error.rb +10 -0
  42. data/lib/automateit/field_manager.rb +103 -0
  43. data/lib/automateit/interpreter.rb +641 -0
  44. data/lib/automateit/package_manager.rb +242 -0
  45. data/lib/automateit/package_manager/apt.rb +63 -0
  46. data/lib/automateit/package_manager/egg.rb +64 -0
  47. data/lib/automateit/package_manager/gem.rb +179 -0
  48. data/lib/automateit/package_manager/portage.rb +69 -0
  49. data/lib/automateit/package_manager/yum.rb +65 -0
  50. data/lib/automateit/platform_manager.rb +47 -0
  51. data/lib/automateit/platform_manager/darwin.rb +30 -0
  52. data/lib/automateit/platform_manager/debian.rb +26 -0
  53. data/lib/automateit/platform_manager/freebsd.rb +25 -0
  54. data/lib/automateit/platform_manager/gentoo.rb +26 -0
  55. data/lib/automateit/platform_manager/lsb.rb +40 -0
  56. data/lib/automateit/platform_manager/struct.rb +78 -0
  57. data/lib/automateit/platform_manager/uname.rb +29 -0
  58. data/lib/automateit/platform_manager/windows.rb +33 -0
  59. data/lib/automateit/plugin.rb +7 -0
  60. data/lib/automateit/plugin/base.rb +32 -0
  61. data/lib/automateit/plugin/driver.rb +218 -0
  62. data/lib/automateit/plugin/manager.rb +232 -0
  63. data/lib/automateit/project.rb +460 -0
  64. data/lib/automateit/root.rb +14 -0
  65. data/lib/automateit/service_manager.rb +79 -0
  66. data/lib/automateit/service_manager/chkconfig.rb +39 -0
  67. data/lib/automateit/service_manager/rc_update.rb +37 -0
  68. data/lib/automateit/service_manager/sysv.rb +126 -0
  69. data/lib/automateit/service_manager/update_rcd.rb +35 -0
  70. data/lib/automateit/shell_manager.rb +261 -0
  71. data/lib/automateit/shell_manager/base_link.rb +67 -0
  72. data/lib/automateit/shell_manager/link.rb +24 -0
  73. data/lib/automateit/shell_manager/portable.rb +421 -0
  74. data/lib/automateit/shell_manager/symlink.rb +32 -0
  75. data/lib/automateit/shell_manager/which.rb +25 -0
  76. data/lib/automateit/tag_manager.rb +63 -0
  77. data/lib/automateit/tag_manager/struct.rb +101 -0
  78. data/lib/automateit/tag_manager/tag_parser.rb +91 -0
  79. data/lib/automateit/tag_manager/yaml.rb +29 -0
  80. data/lib/automateit/template_manager.rb +55 -0
  81. data/lib/automateit/template_manager/base.rb +172 -0
  82. data/lib/automateit/template_manager/erb.rb +17 -0
  83. data/lib/ext/metaclass.rb +17 -0
  84. data/lib/ext/object.rb +18 -0
  85. data/lib/hashcache.rb +22 -0
  86. data/lib/helpful_erb.rb +63 -0
  87. data/lib/nested_error.rb +33 -0
  88. data/lib/queued_logger.rb +68 -0
  89. data/lib/tempster.rb +239 -0
  90. data/misc/index_gem_repository.rb +303 -0
  91. data/misc/setup_egg.rb +12 -0
  92. data/misc/setup_gem_dependencies.sh +7 -0
  93. data/misc/setup_rubygems.sh +21 -0
  94. data/misc/which.cmd +6 -0
  95. data/spec/extras/automateit_service_sysv_test +50 -0
  96. data/spec/extras/scratch.rb +15 -0
  97. data/spec/extras/simple_recipe.rb +8 -0
  98. data/spec/integration/account_manager_spec.rb +218 -0
  99. data/spec/integration/address_manager_linux_spec.rb +119 -0
  100. data/spec/integration/address_manager_portable_spec.rb +30 -0
  101. data/spec/integration/cli_spec.rb +215 -0
  102. data/spec/integration/examples_spec.rb +54 -0
  103. data/spec/integration/examples_spec_editor.rb +71 -0
  104. data/spec/integration/package_manager_spec.rb +104 -0
  105. data/spec/integration/platform_manager_spec.rb +69 -0
  106. data/spec/integration/service_manager_sysv_spec.rb +115 -0
  107. data/spec/integration/shell_manager_spec.rb +471 -0
  108. data/spec/integration/template_manager_erb_spec.rb +31 -0
  109. data/spec/spec_helper.rb +23 -0
  110. data/spec/unit/edit_manager_spec.rb +162 -0
  111. data/spec/unit/field_manager_spec.rb +79 -0
  112. data/spec/unit/hashcache_spec.rb +28 -0
  113. data/spec/unit/interpreter_spec.rb +98 -0
  114. data/spec/unit/platform_manager_spec.rb +44 -0
  115. data/spec/unit/plugins_spec.rb +253 -0
  116. data/spec/unit/tag_manager_spec.rb +189 -0
  117. data/spec/unit/template_manager_erb_spec.rb +137 -0
  118. metadata +249 -0
  119. 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,7 @@
1
+ module AutomateIt
2
+ class Plugin # :nodoc:
3
+ require 'automateit/plugin/base'
4
+ require 'automateit/plugin/manager'
5
+ require 'automateit/plugin/driver'
6
+ end
7
+ 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