automateit 0.70923

Sign up to get free protection for your applications and to get access to all the features.
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