rainman 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ script: "bundle exec rake spec"
2
+ notifications:
3
+ disabled: true
4
+ rvm:
5
+ - 1.8.7
6
+ - 1.9.2
7
+ - 1.9.3
8
+ - ree
9
+ - rbx
10
+ - rbx-2.0
11
+ - jruby
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rainman.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2011 Site5 LLC <http://www.site5.com/>
2
+
3
+ Permission is hereby granted, free of charge, to any person ob-
4
+ taining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without restric-
6
+ tion, including without limitation the rights to use, copy, modi-
7
+ fy, merge, publish, distribute, sublicense, and/or sell copies of
8
+ the Software, and to permit persons to whom the Software is fur-
9
+ nished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
16
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN-
17
+ FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,252 @@
1
+ # Rainman [![Rainman Build Status][Build Icon]][Build Status]
2
+
3
+ Rainman is an experiment in writing drivers and handlers. It is a Ruby
4
+ implementation of the [abstract factory pattern][1]. Abstract factories provide
5
+ the general API used to interact with any number of interfaces. Interfaces
6
+ perform actual operations. Rainman provides a simple DSL for implementing this
7
+ design.
8
+
9
+ [1]: http://en.wikipedia.org/wiki/Abstract_factory_pattern
10
+
11
+ [Build Icon]: https://secure.travis-ci.org/site5/rainman.png?branch=master
12
+ [Build Status]: http://travis-ci.org/site5/rainman
13
+
14
+ ## Drivers & Handlers
15
+
16
+ In Rainman, drivers represent abstract factories and handlers represent the
17
+ interfaces those factories interact with. In simpler terms, drivers define
18
+ _what_ things you can do; handlers define _how_ to do those things.
19
+
20
+ ## Creating a driver
21
+
22
+ Rainman drivers are implemented as Modules. They must be extended with
23
+ `Rainman::Driver` and use the driver DSL to define their public API. An
24
+ example Domain driver might look like this:
25
+
26
+ ```ruby
27
+ require 'rainman'
28
+
29
+ # The Domain module handles creating and deleting domains, and listing
30
+ # nameservers
31
+ module Domain
32
+ extend Rainman::Driver
33
+
34
+ # Register Domain::Abc as a handler.
35
+ register_handler :abc
36
+
37
+ # Register Domain::Xyz as a handler.
38
+ register_handler :xyz
39
+
40
+ # Register Domain.create as a public method
41
+ define_action :create
42
+
43
+ # Register Domain.destroy as a public method
44
+ define_action :destroy
45
+
46
+ # Register Domain.namservers.list as a public method
47
+ namespace :nameservers do
48
+ define_action :list
49
+ end
50
+ end
51
+ ```
52
+
53
+ ## Implementing handlers
54
+
55
+ Driver handlers are implemented as classes. They must be within the namespace
56
+ of the driver Module. Using the example above, here are example handlers for
57
+ Abc and Xyz:
58
+
59
+ ```ruby
60
+ class Domain::Abc
61
+ # Public: Creates a new domain.
62
+ #
63
+ # Returns a Hash.
64
+ def create(params = {})
65
+ end
66
+
67
+ # Public: Destroy a domain
68
+ #
69
+ # Returns true or false.
70
+ def destroy(params = {})
71
+ end
72
+ end
73
+
74
+ class Domain::Xyz
75
+ # Public: Creates a new domain.
76
+ #
77
+ # Returns a Hash.
78
+ def create(params = {})
79
+ end
80
+
81
+ # Public: Destroy a domain
82
+ #
83
+ # Returns true or false.
84
+ def destroy(params = {})
85
+ end
86
+ end
87
+ ```
88
+
89
+ The example driver above also defined `nameservers` namespace with a `list`
90
+ action (eg: `Domain.nameservers.list`). To implement this, a Nameservers class
91
+ is created within each handler's namespace:
92
+
93
+ ```ruby
94
+ class Domain::Abc::Nameserver
95
+
96
+ # Public: Lists nameservers for this domain.
97
+ #
98
+ # Returns an Array.
99
+ def list(params = {})
100
+ end
101
+ end
102
+
103
+ class Domain::Xyz::Nameserver
104
+
105
+ # Public: Lists nameservers for this domain.
106
+ #
107
+ # Returns an Array.
108
+ def list(params = {})
109
+ end
110
+ end
111
+ ```
112
+
113
+ ## Handler setup
114
+ If your handler requires any sort of setup that can't be handled in your
115
+ initialize method (e.g. you're subclassing and can't override
116
+ initialize), you can define a setup_handler method. Rainman will
117
+ automatically call this method for you after the class is initialized.
118
+
119
+ ```ruby
120
+ class Domain::Abc
121
+ attr_accessor :config
122
+
123
+ def initialized
124
+ @config = { :username => 'username', :password => 'password' }
125
+ end
126
+ end
127
+
128
+ class Domain::Xyz < Domain::Abc
129
+ def setup_handler
130
+ @config[:username] = 'other'
131
+ end
132
+ end
133
+ ```
134
+
135
+ ## Using a driver
136
+
137
+ With a driver and handler defined, the driver can now be used in a few
138
+ different ways.
139
+
140
+ ### General
141
+
142
+ A driver's actions are available as singleton methods. By default, actions are
143
+ sent to the current handler, or a default handler if a handler is not currently
144
+ in use.
145
+
146
+ ```ruby
147
+ # Create a domain
148
+ Domain.create({})
149
+
150
+ # Destroy a domain
151
+ Domain.destroy({})
152
+
153
+ # List domain nameservers
154
+ Domain.nameservers.list({})
155
+ ```
156
+
157
+ ### Changing handlers
158
+
159
+ It is possible to change the handler used at runtime using the `with_handler`
160
+ method. This method temporarily changes the current handler. This means, if
161
+ you have a default handler set, and use `with_handler`, that default handler
162
+ is preserved.
163
+
164
+ ```ruby
165
+ Domain.with_handler(:abc) do |driver|
166
+ # Here, current_handler is now set to :abc
167
+ driver.create
168
+ end
169
+ ```
170
+
171
+ You can also change the current handler for the duration of your code/session.
172
+
173
+ ```ruby
174
+ Domain.set_current_handler :xyz
175
+ Domain.create # create an :xyz domain
176
+
177
+ Domain.set_current_handler :abc
178
+ Domain.create # create an :abc domain
179
+ Domain.transfer # transfer an :abc domain
180
+ ```
181
+
182
+ It is highly suggested you stick to using `with_handler` unless you have a
183
+ reason.
184
+
185
+ ### Including drivers in other classes
186
+
187
+ A driver can be included in another class and its actions are available as
188
+ instance methods.
189
+
190
+ ```ruby
191
+ class Service
192
+ include Domain
193
+ end
194
+
195
+ s = Service.new
196
+
197
+ s.create
198
+
199
+ s.destroy
200
+
201
+ s.nameservers.list
202
+
203
+ s.with_handler(:abc) do |handler|
204
+ handler.create
205
+ end
206
+
207
+ s.set_current_handler :xyz
208
+ s.create
209
+ ```
210
+
211
+ If you want to namespace a driver in another class, it's as easy as:
212
+
213
+ ```ruby
214
+ class Service
215
+ def domain
216
+ Domain
217
+ end
218
+ end
219
+
220
+ s = Service.new
221
+
222
+ s.domain.create
223
+
224
+ s.domain.destroy
225
+
226
+ s.domain.nameservers.list
227
+
228
+ s.domain.with_handler(:abc) do |handler|
229
+ handler.create
230
+ end
231
+
232
+ s.domain.set_current_handler :xyz
233
+ ```
234
+
235
+ ## Note on Patches/Pull Requests
236
+
237
+ * Fork the project.
238
+ * Make your feature addition or bug fix.
239
+ * Add tests for it. This is important so I don't break it in a future version
240
+ unintentionally.
241
+ * Commit, do not bump version. (If you want to have your own version, that is
242
+ fine but bump version in a commit by itself I can ignore when I pull).
243
+ * Send me a pull request. Bonus points for topic branches.
244
+
245
+ ## Contributors
246
+
247
+ * [Justin Mazzi](https://github.com/jmazzi)
248
+ * [Joshua Priddle](https://github.com/itspriddle)
249
+
250
+ ## Copyright
251
+
252
+ Copyright (c) 2011 Site5 LLC. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+ rescue LoadError
6
+ puts "Please install rspec (bundle install)"
7
+ exit
8
+ end
9
+
10
+ RSpec::Core::RakeTask.new :spec
11
+ task :default => :spec
12
+
13
+ desc "Open an irb session preloaded with this library"
14
+ task :console do
15
+ sh "irb -rubygems -r ./lib/rainman.rb -I ./lib -r ./example/domain.rb"
16
+ end
@@ -0,0 +1,10 @@
1
+ module Domain
2
+ # This class handles interacting with the Enom API's nameserver functions.
3
+ class Enom::Nameservers
4
+
5
+ # List domain nameservers
6
+ def list(*a)
7
+ :enom_ns_list
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Domain
2
+ # This class handles interacting with the Enom API.
3
+ class Enom
4
+ def list(*a)
5
+ :enom_list
6
+ end
7
+
8
+ # Transfer a domain name
9
+ def transfer(*a)
10
+ :enom_transfer
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module Domain
2
+ # This class handles interacting with the Opensrs API's nameserver functions.
3
+ class Opensrs::Nameservers
4
+
5
+ # List domain nameservers
6
+ def list(*a)
7
+ :opensrs_ns_list
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Domain
2
+ # This class handles interacting with the Opensrs API.
3
+ class Opensrs
4
+ def list(*a)
5
+ :opensrs_list
6
+ end
7
+
8
+ # Transfer a domain name
9
+ def transfer(*a)
10
+ :opensrs_transfer
11
+ end
12
+ end
13
+ end
data/example/domain.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'rainman'
2
+
3
+ # Load handlers
4
+ $:.unshift File.expand_path('..', __FILE__)
5
+ require 'domain/enom'
6
+ require 'domain/enom/nameservers'
7
+ require 'domain/opensrs'
8
+ require 'domain/opensrs/nameservers'
9
+
10
+ # The Domain module will contain all methods for interacting with various
11
+ # domain handlers.
12
+ module Domain
13
+ extend Rainman::Driver
14
+
15
+ register_handler :enom
16
+
17
+ register_handler :opensrs
18
+
19
+ namespace :nameservers do
20
+ define_action :list
21
+ end
22
+
23
+ define_action :list
24
+
25
+ define_action :transfer
26
+
27
+ set_default_handler :opensrs
28
+ end
@@ -0,0 +1,287 @@
1
+ require "forwardable"
2
+
3
+ module Rainman
4
+ # The Rainman::Driver module contains methods for defining Drivers and
5
+ # proxying their associated actions to the appropriate handlers.
6
+ module Driver
7
+ # Public: Extended hook; this is run when a module extends itself with
8
+ # the Rainman::Driver module.
9
+ def self.extended(base)
10
+ all << base
11
+ base.extend(base)
12
+ end
13
+
14
+ # Public: Get a list of all Drivers (eg: Modules that are extended with
15
+ # Rainman::Driver).
16
+ #
17
+ # Returns an Array.
18
+ def self.all
19
+ @all ||= []
20
+ end
21
+
22
+ # Public: Registered handlers.
23
+ #
24
+ # Keys are the handler name (eg: :my_handler); values are the handler
25
+ # class (eg: MyHandler).
26
+ #
27
+ # Raises NoHandler if an attempt to access a key of nil is made, (eg:
28
+ # handlers[nil]).
29
+ #
30
+ # Raises InvalidHandler if an attempt to access an invalid key is made.
31
+ #
32
+ # Returns a Hash.
33
+ def handlers
34
+ @handlers ||= Hash.new do |hash, key|
35
+ if key.nil?
36
+ raise NoHandler
37
+ else
38
+ raise InvalidHandler, key
39
+ end
40
+ end
41
+ end
42
+
43
+ # Public: Temporarily change a Driver's current handler. The handler is
44
+ # changed for the duration of the block supplied. This is useful to perform
45
+ # actions using multiple handlers without changing defaults.
46
+ #
47
+ # name - The Symbol name of the handler to use.
48
+ #
49
+ # Example
50
+ #
51
+ # with_handler(:enom) do |handler|
52
+ # handler.transfer
53
+ # end
54
+ #
55
+ # Yields a Runner instance if a block is given.
56
+ #
57
+ # Returns a Runner instance or the result of a block.
58
+ def with_handler(name, &block)
59
+ raise MissingBlock, :with_handler unless block_given?
60
+
61
+ old_handler = current_handler
62
+
63
+ begin
64
+ set_current_handler name
65
+ yield current_handler_instance.runner
66
+ ensure
67
+ set_current_handler old_handler
68
+ end
69
+ end
70
+
71
+ # Public: Sets the default handler used for this Driver.
72
+ #
73
+ # name - The Symbol name to set as the default handler. Should be a key
74
+ # from handlers.
75
+ #
76
+ # Returns the Symbol name.
77
+ def set_default_handler(name)
78
+ @default_handler = name
79
+ end
80
+
81
+ # Public: Get the default handler used for this Driver.
82
+ #
83
+ # Returns the Symbol name of this Driver's default handler.
84
+ def default_handler
85
+ @default_handler
86
+ end
87
+
88
+ # Public: Sets the current handler. Name should be an underscored symbol
89
+ # representing a class name in the current context.
90
+ #
91
+ # name - The Symbol name of the handler to use. Can be set to nil to
92
+ # clear the current handler.
93
+ #
94
+ # Example
95
+ #
96
+ # set_current_handler :my_handler #=> sets handler to MyHandler
97
+ # set_current_handler nil #=> clears handler
98
+ #
99
+ # Returns the Symbol name of the current handler or nothing.
100
+ def set_current_handler(name)
101
+ @current_handler = name
102
+ end
103
+
104
+ private
105
+
106
+ # Private: Included hook; this is invoked when a Driver module is
107
+ # included in another class. It sets up delegation so that the including
108
+ # class can access a Driver's singleton methods as instance methods.
109
+ #
110
+ # base - The Module/Class that included this module.
111
+ #
112
+ # Example
113
+ #
114
+ # class Service
115
+ # include Domain
116
+ # end
117
+ #
118
+ # s = Service.new
119
+ # s.transfer #=> calls Domain.transfer
120
+ #
121
+ # Returns nothing.
122
+ def included(base)
123
+ base.extend(::Forwardable)
124
+ base.def_delegators self, *singleton_methods
125
+ end
126
+
127
+ # Private: A hash containing handler instances. This prevents handlers
128
+ # from being initialized multiple times in a single session.
129
+ #
130
+ # Returns a Hash containing instances of handlers that have been
131
+ # initialized.
132
+ def handler_instances
133
+ @handler_instances ||= {}
134
+ end
135
+
136
+ # Private: Get or set an instance of the current handler class. This
137
+ # method stores the instance in handler_instances, and should be used
138
+ # instead of manually initializing handlers.
139
+ #
140
+ # Returns an instance of the current handler class.
141
+ def current_handler_instance
142
+ handler_instances[current_handler] ||= handlers[current_handler].new.tap do |_handler|
143
+ _handler.setup_handler if _handler.respond_to?(:setup_handler)
144
+ end
145
+ end
146
+
147
+ # Private: Get the current handler in use by this Driver.
148
+ #
149
+ # Returns the Symbol name of the current handler, or default handler if
150
+ # a current handler is not set.
151
+ def current_handler
152
+ @current_handler || @default_handler
153
+ end
154
+
155
+ # Private: Register a handler for use with the current Driver.
156
+ #
157
+ # If a block is given it is evaluated within the context of the handler
158
+ # Class.
159
+ #
160
+ # name - The Symbol handler name.
161
+ # opts - A Hash containing optional arguments:
162
+ # :class_name - The class name to use.
163
+ #
164
+ # Examples
165
+ #
166
+ # register_handler :bob
167
+ #
168
+ # Returns the handler Class.
169
+ def register_handler(name, opts = {})
170
+ opts.reverse_merge!(
171
+ :class_name => "#{self.name}::#{name.to_s.camelize}"
172
+ )
173
+
174
+ klass = opts[:class_name].constantize
175
+
176
+ handlers[name] = inject_handler_methods(klass, name.to_sym)
177
+ end
178
+
179
+ # Private: Create a new namespace.
180
+ #
181
+ # name - The Symbol handler name.
182
+ # opts - Arguments (unused currently).
183
+ # block - A required block used to create actions within the namespace
184
+ #
185
+ # Example
186
+ #
187
+ # namespace :nameservers do
188
+ # define_action :list
189
+ # end
190
+ #
191
+ # Raises Rainman::MissingBlock if called without a block.
192
+ #
193
+ # Returns a Module.
194
+ def namespace(name, opts = {}, &block)
195
+ raise MissingBlock, :namespace unless block_given?
196
+
197
+ create_method(name) do
198
+ key = "@#{name}"
199
+
200
+ if instance_variable_defined?(key)
201
+ ns = instance_variable_get(key)
202
+ else
203
+ ns = instance_variable_set(key, {})
204
+ end
205
+
206
+ unless ns[current_handler]
207
+ mod = Module.new do
208
+ extend self
209
+ extend ActionMethods
210
+ class << self
211
+ attr_accessor :current_handler_instance
212
+ end
213
+ end
214
+
215
+ klass = current_handler_instance.class.const_get(name.to_s.camelize)
216
+ mod.current_handler_instance = inject_handler_methods(klass, name).new
217
+
218
+ mod.instance_eval(&block)
219
+ ns[current_handler] = mod
220
+ end
221
+
222
+ ns[current_handler]
223
+ end
224
+ end
225
+
226
+ # Private: Injects Handler methods into the given class/module.
227
+ #
228
+ # base - The base Class/Module.
229
+ # handler_name - The Symbol name of the handler class.
230
+ #
231
+ # Example
232
+ #
233
+ # inject_handler_methods(SomeHandler, :some_handler)
234
+ #
235
+ # Returns base Class/Module.
236
+ def inject_handler_methods(base, handler_name)
237
+ base.extend(Handler)
238
+ base.instance_variable_set(:@handler_name, handler_name)
239
+ base.instance_variable_set(:@parent_klass, self)
240
+ base
241
+ end
242
+
243
+ # These methods are used to create handler actions.
244
+ module ActionMethods
245
+ # Private: Define a new action.
246
+ #
247
+ # name - The Symbol handler name.
248
+ # opts - Options (unused currently).
249
+ #
250
+ # Example
251
+ #
252
+ # define_action :blah
253
+ #
254
+ # Returns a Proc.
255
+ def define_action(name, opts = {})
256
+ create_method(name) do |*args, &block|
257
+ current_handler_instance.runner.send(name, *args, &block)
258
+ end
259
+ end
260
+
261
+ # Private: Creates a new method.
262
+ #
263
+ # method - The method name.
264
+ # args - Arguments to be supplied to the method (optional).
265
+ # block - Block to be supplied to the method (optional).
266
+ #
267
+ # Examples
268
+ #
269
+ # create_method :blah do
270
+ # # code to execute
271
+ # end
272
+ #
273
+ # Raises Rainman::AlreadyImplemented if the method already exists.
274
+ #
275
+ # Returns a Proc.
276
+ def create_method(method, *args, &block)
277
+ if respond_to?(method, true)
278
+ raise AlreadyImplemented, "#{inspect}::#{method}"
279
+ else
280
+ define_method(method, *args, &block)
281
+ end
282
+ end
283
+ end
284
+
285
+ include ActionMethods
286
+ end
287
+ end