automate-it 0.9.0

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 (137) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.hgignore +10 -0
  4. data/.loadpath +5 -0
  5. data/.project +17 -0
  6. data/CHANGES.txt +314 -0
  7. data/Hoe.rake +40 -0
  8. data/Manifest.txt +164 -0
  9. data/README.txt +40 -0
  10. data/Rakefile +256 -0
  11. data/TESTING.txt +57 -0
  12. data/TODO.txt +50 -0
  13. data/TUTORIAL.txt +391 -0
  14. data/automate-it.gemspec +25 -0
  15. data/bin/ai +3 -0
  16. data/bin/aifield +75 -0
  17. data/bin/aissh +93 -0
  18. data/bin/aitag +134 -0
  19. data/bin/automateit +133 -0
  20. data/docs/friendly_errors.txt +50 -0
  21. data/docs/previews.txt +86 -0
  22. data/examples/basic/Rakefile +26 -0
  23. data/examples/basic/config/automateit_env.rb +16 -0
  24. data/examples/basic/config/fields.yml +3 -0
  25. data/examples/basic/config/tags.yml +7 -0
  26. data/examples/basic/dist/README.txt +9 -0
  27. data/examples/basic/dist/myapp_server.erb +30 -0
  28. data/examples/basic/install.log +15 -0
  29. data/examples/basic/lib/README.txt +10 -0
  30. data/examples/basic/recipes/README.txt +4 -0
  31. data/examples/basic/recipes/install.rb +61 -0
  32. data/examples/basic/recipes/uninstall.rb +6 -0
  33. data/gpl.txt +674 -0
  34. data/helpers/cpan_wrapper.pl +220 -0
  35. data/helpers/which.cmd +7 -0
  36. data/lib/automateit.rb +55 -0
  37. data/lib/automateit/account_manager.rb +114 -0
  38. data/lib/automateit/account_manager/base.rb +138 -0
  39. data/lib/automateit/account_manager/etc.rb +128 -0
  40. data/lib/automateit/account_manager/nscd.rb +33 -0
  41. data/lib/automateit/account_manager/passwd_expect.rb +40 -0
  42. data/lib/automateit/account_manager/passwd_pty.rb +69 -0
  43. data/lib/automateit/account_manager/posix.rb +138 -0
  44. data/lib/automateit/address_manager.rb +88 -0
  45. data/lib/automateit/address_manager/base.rb +171 -0
  46. data/lib/automateit/address_manager/bsd.rb +28 -0
  47. data/lib/automateit/address_manager/freebsd.rb +59 -0
  48. data/lib/automateit/address_manager/linux.rb +42 -0
  49. data/lib/automateit/address_manager/openbsd.rb +66 -0
  50. data/lib/automateit/address_manager/portable.rb +37 -0
  51. data/lib/automateit/address_manager/sunos.rb +34 -0
  52. data/lib/automateit/cli.rb +85 -0
  53. data/lib/automateit/common.rb +65 -0
  54. data/lib/automateit/constants.rb +35 -0
  55. data/lib/automateit/download_manager.rb +48 -0
  56. data/lib/automateit/edit_manager.rb +321 -0
  57. data/lib/automateit/error.rb +10 -0
  58. data/lib/automateit/field_manager.rb +103 -0
  59. data/lib/automateit/interpreter.rb +631 -0
  60. data/lib/automateit/package_manager.rb +257 -0
  61. data/lib/automateit/package_manager/apt.rb +27 -0
  62. data/lib/automateit/package_manager/cpan.rb +101 -0
  63. data/lib/automateit/package_manager/dpkg.rb +54 -0
  64. data/lib/automateit/package_manager/egg.rb +64 -0
  65. data/lib/automateit/package_manager/gem.rb +201 -0
  66. data/lib/automateit/package_manager/pear.rb +95 -0
  67. data/lib/automateit/package_manager/pecl.rb +80 -0
  68. data/lib/automateit/package_manager/portage.rb +69 -0
  69. data/lib/automateit/package_manager/yum.rb +65 -0
  70. data/lib/automateit/platform_manager.rb +49 -0
  71. data/lib/automateit/platform_manager/darwin.rb +30 -0
  72. data/lib/automateit/platform_manager/debian.rb +26 -0
  73. data/lib/automateit/platform_manager/freebsd.rb +29 -0
  74. data/lib/automateit/platform_manager/gentoo.rb +26 -0
  75. data/lib/automateit/platform_manager/lsb.rb +44 -0
  76. data/lib/automateit/platform_manager/openbsd.rb +28 -0
  77. data/lib/automateit/platform_manager/struct.rb +80 -0
  78. data/lib/automateit/platform_manager/sunos.rb +39 -0
  79. data/lib/automateit/platform_manager/uname.rb +29 -0
  80. data/lib/automateit/platform_manager/windows.rb +40 -0
  81. data/lib/automateit/plugin.rb +7 -0
  82. data/lib/automateit/plugin/base.rb +32 -0
  83. data/lib/automateit/plugin/driver.rb +256 -0
  84. data/lib/automateit/plugin/manager.rb +224 -0
  85. data/lib/automateit/project.rb +493 -0
  86. data/lib/automateit/root.rb +17 -0
  87. data/lib/automateit/service_manager.rb +93 -0
  88. data/lib/automateit/service_manager/chkconfig.rb +39 -0
  89. data/lib/automateit/service_manager/rc_update.rb +37 -0
  90. data/lib/automateit/service_manager/sysv.rb +139 -0
  91. data/lib/automateit/service_manager/update_rcd.rb +35 -0
  92. data/lib/automateit/shell_manager.rb +316 -0
  93. data/lib/automateit/shell_manager/base_link.rb +67 -0
  94. data/lib/automateit/shell_manager/link.rb +24 -0
  95. data/lib/automateit/shell_manager/portable.rb +523 -0
  96. data/lib/automateit/shell_manager/symlink.rb +32 -0
  97. data/lib/automateit/shell_manager/which_base.rb +30 -0
  98. data/lib/automateit/shell_manager/which_unix.rb +16 -0
  99. data/lib/automateit/shell_manager/which_windows.rb +20 -0
  100. data/lib/automateit/tag_manager.rb +127 -0
  101. data/lib/automateit/tag_manager/struct.rb +121 -0
  102. data/lib/automateit/tag_manager/tag_parser.rb +93 -0
  103. data/lib/automateit/tag_manager/yaml.rb +29 -0
  104. data/lib/automateit/template_manager.rb +56 -0
  105. data/lib/automateit/template_manager/base.rb +181 -0
  106. data/lib/automateit/template_manager/erb.rb +17 -0
  107. data/lib/ext/metaclass.rb +17 -0
  108. data/lib/ext/object.rb +18 -0
  109. data/lib/ext/shell_escape.rb +7 -0
  110. data/lib/hashcache.rb +22 -0
  111. data/lib/helpful_erb.rb +63 -0
  112. data/lib/inactive_support.rb +53 -0
  113. data/lib/inactive_support/basic_object.rb +6 -0
  114. data/lib/inactive_support/clean_logger.rb +127 -0
  115. data/lib/inactive_support/core_ext/array/extract_options.rb +19 -0
  116. data/lib/inactive_support/core_ext/blank.rb +50 -0
  117. data/lib/inactive_support/core_ext/class/attribute_accessors.rb +48 -0
  118. data/lib/inactive_support/core_ext/class/inheritable_attributes.rb +140 -0
  119. data/lib/inactive_support/core_ext/enumerable.rb +63 -0
  120. data/lib/inactive_support/core_ext/hash/keys.rb +54 -0
  121. data/lib/inactive_support/core_ext/module/aliasing.rb +70 -0
  122. data/lib/inactive_support/core_ext/numeric/time.rb +91 -0
  123. data/lib/inactive_support/core_ext/string/inflections.rb +153 -0
  124. data/lib/inactive_support/core_ext/symbol.rb +14 -0
  125. data/lib/inactive_support/core_ext/time/conversions.rb +96 -0
  126. data/lib/inactive_support/duration.rb +96 -0
  127. data/lib/inactive_support/inflections.rb +53 -0
  128. data/lib/inactive_support/inflector.rb +282 -0
  129. data/lib/nested_error.rb +33 -0
  130. data/lib/nitpick.rb +33 -0
  131. data/lib/queued_logger.rb +68 -0
  132. data/lib/tempster.rb +250 -0
  133. data/misc/index_gem_repository.rb +304 -0
  134. data/misc/setup_egg.rb +12 -0
  135. data/misc/setup_gem_dependencies.sh +6 -0
  136. data/misc/setup_rubygems.sh +21 -0
  137. metadata +279 -0
@@ -0,0 +1,40 @@
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
+
20
+ # VER values: http://www.ss64.com/nt/ver.html
21
+ ver = `ver`.strip
22
+ if match = ver.match(/Windows (\w+)/)
23
+ @struct[:release] = match[1].downcase
24
+ elsif match = ver.match(/Windows \[Version 6\.0\./)
25
+ @struct[:release] = "vista"
26
+ end
27
+
28
+ @struct
29
+ end
30
+ private :_prepare
31
+
32
+ def query(search)
33
+ _prepare
34
+ super(search)
35
+ end
36
+
37
+ def single_vendor?
38
+ return true
39
+ end
40
+ 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,256 @@
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
+ # Returns the Plugin::Manager instance for this Driver.
49
+ attr_accessor :manager
50
+
51
+ # Setup a Driver.
52
+ #
53
+ # Options:
54
+ # * :manager -- The Plugin::Manager instance controlling this driver.
55
+ def setup(opts={})
56
+ self.manager = opts[:manager] if opts[:manager]
57
+ super(opts)
58
+ end
59
+
60
+ # Retrieve the manager token for this driver
61
+ def self.manager_token
62
+ fragments = base_driver.to_s.split(/::/)
63
+ return fragments[fragments.size-2].underscore.to_sym
64
+ end
65
+
66
+ BASE_DRIVER_NAME = "BaseDriver"
67
+
68
+ # Is this a base driver?
69
+ def self.base_driver?
70
+ to_s =~ /::#{BASE_DRIVER_NAME}/
71
+ end
72
+
73
+ # Retrieve the base driver class for this driver.
74
+ def self.base_driver
75
+ ancestors.select{|t| t.to_s =~ /::#{BASE_DRIVER_NAME}/}.last
76
+ end
77
+
78
+ # Register drivers. Concrete drivers are added to a class-wide data
79
+ # structure which maps them to the manager they belong to.
80
+ def self.inherited(subclass) # :nodoc:
81
+ base_driver = subclass.base_driver
82
+ if subclass.base_driver?
83
+ # Ignore, base drivers should never be registered
84
+ elsif base_driver
85
+ manager_token = subclass.manager_token
86
+ classes[manager_token] ||= []
87
+
88
+ unless classes[manager_token].include?(subclass)
89
+ classes[manager_token] << subclass
90
+ end
91
+
92
+ ### puts "manager_token %s / driver %s" % [manager_token, subclass]
93
+ else
94
+ # XXX Should this really raise an exception?
95
+ raise TypeError.new("Can't determine manager for driver: #{subclass}")
96
+ end
97
+ end
98
+
99
+ # Declare that this driver class is abstract. It can be subclassed but
100
+ # will not be instantiated by the Interpreter's Managers. BaseDriver
101
+ # classes are automatically declared abstract.
102
+ def self.abstract_driver
103
+ if base_driver?
104
+ # Ignore, base drivers should never have been registered
105
+ elsif manager = manager_token
106
+ classes[manager].delete(self)
107
+ else
108
+ raise TypeError.new("Can't find manager for abstract plugin: #{self}")
109
+ end
110
+ end
111
+
112
+ # Defines resources this driver depends on.
113
+ #
114
+ # Options:
115
+ # * :files -- Array of filenames that must exist.
116
+ # * :directories -- Array of directories that must exist.
117
+ # * :programs -- Array of programs, checked with +which+, that must exist.
118
+ # * :callbacks -- Array of lambdas that must return true.
119
+ # * :libraries -- Array of libraries to +require+.
120
+ #
121
+ # Example:
122
+ # class APT < Plugin::Driver
123
+ # depends_on :programs => %w(apt-get dpkg)
124
+ # # ...
125
+ # end
126
+ def self.depends_on(opts)
127
+ meta_eval do
128
+ attr_accessor :_depends_on_opts, :_is_available, :_missing_dependencies
129
+ end
130
+ self._depends_on_opts = opts
131
+ end
132
+
133
+ # Is this driver available on this platform? Queries the dependencies set
134
+ # by #depends_on to make sure that they're all present, otherwise raises
135
+ # a NotImplementedError. If a driver author needs to do some other kind
136
+ # of check, it's reasonable to override this method.
137
+ #
138
+ # For example, this method is used by the PackageManager driver for APT
139
+ # to determine if the "apt-get" program is installed.
140
+ #
141
+ # What's the difference between #available? and #suitability? The
142
+ # #available? method is used to determine if the driver *can* do the
143
+ # work, while the #suitability method determines if the driver *should*
144
+ # be automatically selected.
145
+ def available?
146
+ # Some drivers don't run +depends_on+, so assume they're available.
147
+ return true unless self.class.respond_to?(:_depends_on_opts)
148
+ opts = self.class._depends_on_opts
149
+
150
+ # Driver said that it +depends_on :nothing+, so it's available.
151
+ return true if opts == :nothing
152
+
153
+ is_available = self.class._is_available
154
+ if is_available.nil? and opts.nil?
155
+ #log.debug(PNOTE+"don't know if driver #{self.class} is available, maybe it doesn't state what it +depends_on+")
156
+ return false
157
+ elsif is_available.nil?
158
+ all_present = true
159
+ missing = {}
160
+
161
+ # Check callbacks last
162
+ kinds = opts.keys
163
+ callbacks = kinds.delete(:callbacks)
164
+ kinds << callbacks if callbacks
165
+
166
+ begin
167
+ for kind in kinds
168
+ next unless opts[kind]
169
+ for item in [opts[kind]].flatten
170
+ present = \
171
+ case kind
172
+ when :files
173
+ File.exists?(item)
174
+ when :directories
175
+ File.directory?(item)
176
+ when :programs
177
+ # 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.
178
+ result = nil
179
+ for variant in %w(unix windows)
180
+ variant_token = "which_#{variant}".to_sym
181
+ begin
182
+ driver = interpreter.shell_manager[variant_token]
183
+ result = driver.which!(item)
184
+ ### puts "%s : %s for %s" % [variant, result, item]
185
+ break
186
+ rescue ArgumentError, NotImplementedError, NoMethodError => e
187
+ # Exceptions are expected, only print for debugging
188
+ ### puts e.inspect
189
+ end
190
+ end
191
+ result
192
+ when :requires, :libraries
193
+ begin
194
+ require item
195
+ true
196
+ rescue LoadError
197
+ false
198
+ end
199
+ when :callbacks
200
+ item.call() ? true : false
201
+ else
202
+ raise TypeError.new("Unknown kind: #{kind}")
203
+ end
204
+ unless present
205
+ all_present = false
206
+ missing[kind] ||= []
207
+ missing[kind] << item
208
+
209
+ # Do not continue scanning if dependency is missed
210
+ raise EOFError.new("break")
211
+ end
212
+ end
213
+ end
214
+ rescue EOFError => e
215
+ # Ignore expected "break" warning
216
+ raise e unless e.message == "break"
217
+ end
218
+ self.class._missing_dependencies = missing
219
+ self.class._is_available = all_present
220
+ log.debug(PNOTE+"Driver #{self.class} #{all_present ? "is" : "isn't"} available")
221
+ end
222
+ return self.class._is_available
223
+ end
224
+
225
+ # Raise a NotImplementedError if this driver is called but is not
226
+ # #available?.
227
+ def _raise_unless_available
228
+ unless available?
229
+ msg = ""
230
+ for kind, elements in self.class._missing_dependencies
231
+ msg << "; " unless msg == ""
232
+ msg << "%s: %s" % [kind, elements.sort.join(', ')]
233
+ end
234
+ raise NotImplementedError.new("Missing dependencies -- %s" % msg)
235
+ end
236
+ end
237
+ protected :_raise_unless_available
238
+
239
+ # What is this driver's suitability for automatic detection? The Manager
240
+ # queries its drivers when there isn't a driver specified with a
241
+ # <tt>:with</tt> or #default so it can choose a suitable driver for the
242
+ # +method+ and +args+. Any driver that returns an integer 1 or greater
243
+ # claims to be suitable. The Manager will then select the driver with the
244
+ # highest suitability level. Drivers that return an integer less than 1
245
+ # are excluded from automatic detection.
246
+ #
247
+ # The <tt>available?</tt> method is used to determine if the driver can
248
+ # do the work, while the <tt>suitability</tt> method determines if the
249
+ # driver should be automatically selected.
250
+ def suitability(method, *args, &block)
251
+ log.debug("driver #{self.class} doesn't implement the +suitability+ method")
252
+ return -1
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,224 @@
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
+ self.aliased_methods ||= Set.new
42
+ self.aliased_methods.merge(methods.flatten)
43
+ end
44
+
45
+ # Drivers for this manager as a hash of driver tokens to driver
46
+ # instances.
47
+ attr_accessor :drivers
48
+
49
+ # Driver classes used by this Manager.
50
+ def self.driver_classes
51
+ Driver.classes[token] || []
52
+ end
53
+
54
+ # Options:
55
+ # * :default -- The token of the driver to set as the default.
56
+ def setup(opts={})
57
+ super(opts)
58
+
59
+ @default ||= nil
60
+
61
+ instantiate_drivers
62
+
63
+ if driver = opts[:default] || opts[:driver]
64
+ default(opts[:default]) if opts[:default]
65
+ @drivers[driver].setup(opts)
66
+ end
67
+ end
68
+
69
+ # Instantiate drivers for this manager. This method is smart enough that
70
+ # it can be called multiple times and will only instantiate drivers it
71
+ # hasn't instantiated yet. All drivers will share an instance of the
72
+ # Interpreter, thus providing common state storage.
73
+ def instantiate_drivers
74
+ @drivers ||= {}
75
+ self.class.driver_classes.each do |driver_class|
76
+ driver_token = driver_class.token
77
+ unless @drivers[driver_token]
78
+ @drivers[driver_token] = driver_class.allocate
79
+ end
80
+ end
81
+ self.class.driver_classes.each do |driver_class|
82
+ driver_token = driver_class.token
83
+ @drivers[driver_token].setup(
84
+ :interpreter => @interpreter, :manager => self)
85
+ end
86
+ end
87
+
88
+ # Returns the Driver with the specified token. E.g., +:apt+ will return
89
+ # the +APT+ driver.
90
+ def [](token)
91
+ return @drivers[token]
92
+ end
93
+
94
+ # Manipulate the default driver. Without arguments, gets the driver token
95
+ # as a symbol. With argument, sets the default driver to the +token+,
96
+ # e.g., the argument <tt>:apt</tt> will make the +APT+ driver the default.
97
+ def default(token=nil)
98
+ if token.nil?
99
+ @default
100
+ else
101
+ @default = token
102
+ end
103
+ end
104
+
105
+ # Set the default driver to the +token+. See also #default.
106
+ def default=(token)
107
+ default(token)
108
+ end
109
+
110
+ # Dispatch a method by guessing its name. This is the recommended way to
111
+ # write wrappers for a Manager methods.
112
+ #
113
+ # Example:
114
+ # class MyManager < Plugin::Manager
115
+ # # Your RDoc here
116
+ # def my_method(*args)
117
+ # # Will guess that you want to +dispatch_to+ the +my_method+ by
118
+ # # introspecting the name of the wrapper method.
119
+ # dispatch(*args)
120
+ # end
121
+ # ...
122
+ # end
123
+ def dispatch(*args, &block)
124
+ # Extract caller's method as symbol to save user from having to specify it
125
+ method = caller[0].match(/:in `(.+?)'/)[1].to_sym
126
+ dispatch_to(method, *args, &block)
127
+ end
128
+
129
+ # Dispatch the +method+ with +args+ and +block+ to the appropriate
130
+ # driver. If the arguments include an option of the form <tt>:with =>
131
+ # :mytoken</tt> argument, then the method will be dispatched to the
132
+ # driver represented by <tt>:mytoken</tt>. If no :with argument is
133
+ # specified, the most-suitable driver will be automatically selected. If
134
+ # no suitable driver is available, a NotImplementedError will be raised.
135
+ #
136
+ # Examples:
137
+ # # Run 'hostnames' method on most appropriate AddressManager driver:
138
+ # address_manager.dispatch_to(:hostnames)
139
+ #
140
+ # # Run AddressManager::Portable#hostnames
141
+ # address_manager.dispatch_to(:hostnames, :with => :portable)
142
+ #
143
+ # You will typically not want to use this method directly and instead
144
+ # write wrappers that call #dispatch because it can guess the name of
145
+ # the +method+ argument for you.
146
+ def dispatch_to(method, *args, &block)
147
+ list, options = args_and_opts(*args)
148
+ driver = \
149
+ if options and options[:with]
150
+ @drivers[options[:with].to_sym]
151
+ elsif default
152
+ @drivers[default.to_sym]
153
+ else
154
+ driver_for(method, *args, &block)
155
+ end
156
+ driver.send(method, *args, &block)
157
+ end
158
+
159
+ # Same as #dispatch_to but returns nil if couldn't dispatch, rather than
160
+ # raising an exception.
161
+ def dispatch_safely_to(method, *args, &block)
162
+ begin
163
+ dispatch_to(method, *args, &block)
164
+ rescue NotImplementedError
165
+ nil
166
+ end
167
+ end
168
+
169
+ # Same as #dispatch but returns nil if couldn't dispatch, rather than
170
+ # raising an exception.
171
+ def dispatch_safely(*args, &block)
172
+ method = caller[0].match(/:in `(.+?)'/)[1].to_sym
173
+ dispatch_safely_to(method, *args, &block)
174
+ end
175
+
176
+ # Returns structure which helps choose a suitable driver for the +method+
177
+ # and +args+. Result is a hash of driver tokens and their suitability
178
+ # levels.
179
+ #
180
+ # For example, if we ask the AddressManager for suitability levels for
181
+ # the AddressManager#hostnames method, we might find that there are two
182
+ # drivers (:portable is the token for AddressManager::Portable) and that
183
+ # the :linux driver is most appropriate because it has the highest
184
+ # suitability level:
185
+ #
186
+ # address_manager.driver_suitability_levels_for(:hostnames)
187
+ # # => {:portable=>1, :linux=>2}
188
+ def driver_suitability_levels_for(method, *args, &block)
189
+ results = {}
190
+ @drivers.each_pair do |name, driver|
191
+ next unless driver.respond_to?(method)
192
+ results[name] = driver.suitability(method, *args, &block)
193
+ end
194
+ return results
195
+ end
196
+
197
+ # Get the most suitable driver for this +method+ and +args+. Uses
198
+ # automatic-detection routines and returns the most suitable driver
199
+ # instance found, else raises a NotImplementedError if no suitable driver
200
+ # is found.
201
+ def driver_for(method, *args, &block)
202
+ driver, level = driver_suitability_levels_for(method, *args, &block).sort_by{|k,v| v}.last
203
+ if driver and level > 0
204
+ return @drivers[driver]
205
+ else
206
+ raise NotImplementedError.new("can't find driver for method '#{method}' with arguments: #{args.inspect}")
207
+ end
208
+ end
209
+
210
+ # Is a driver available for this +method+ and +args+? Uses
211
+ # automatic-detection routines and returns a boolean to indicate if a
212
+ # suitable driver is available. Unlike #driver_for, this will not raise
213
+ # an exception.
214
+ def available?(method, *args, &block)
215
+ begin
216
+ driver_for(method, *args, &block)
217
+ true
218
+ rescue NotImplementedError
219
+ false
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end