rainman 0.1.0

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