strelka 0.0.1.pre148 → 0.0.1.pre177

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 (43) hide show
  1. data/ChangeLog +1294 -0
  2. data/IDEAS.rdoc +6 -0
  3. data/Manifest.txt +20 -0
  4. data/README.rdoc +8 -2
  5. data/Rakefile +9 -7
  6. data/examples/auth-demo.rb +32 -0
  7. data/examples/auth-demo2.rb +37 -0
  8. data/examples/auth-form.tmpl +10 -0
  9. data/examples/auth-success.tmpl +3 -0
  10. data/examples/config.yml +12 -0
  11. data/examples/examples.css +4 -0
  12. data/examples/examples.html +31 -0
  13. data/examples/gen-config.rb +5 -2
  14. data/examples/layout.tmpl +31 -0
  15. data/lib/strelka/app/auth.rb +480 -0
  16. data/lib/strelka/app/sessions.rb +8 -6
  17. data/lib/strelka/app/templating.rb +78 -17
  18. data/lib/strelka/app.rb +13 -5
  19. data/lib/strelka/authprovider/basic.rb +134 -0
  20. data/lib/strelka/authprovider/hostaccess.rb +91 -0
  21. data/lib/strelka/authprovider.rb +122 -0
  22. data/lib/strelka/cookie.rb +1 -1
  23. data/lib/strelka/cookieset.rb +1 -1
  24. data/lib/strelka/httprequest/auth.rb +31 -0
  25. data/lib/strelka/logging.rb +69 -14
  26. data/lib/strelka/mixins.rb +35 -65
  27. data/lib/strelka/session/db.rb +115 -0
  28. data/lib/strelka/session/default.rb +38 -49
  29. data/lib/strelka/session.rb +1 -1
  30. data/lib/strelka.rb +4 -1
  31. data/spec/lib/helpers.rb +8 -3
  32. data/spec/strelka/app/auth_spec.rb +367 -0
  33. data/spec/strelka/authprovider/basic_spec.rb +192 -0
  34. data/spec/strelka/authprovider/hostaccess_spec.rb +70 -0
  35. data/spec/strelka/authprovider_spec.rb +99 -0
  36. data/spec/strelka/cookie_spec.rb +1 -1
  37. data/spec/strelka/httprequest/auth_spec.rb +55 -0
  38. data/spec/strelka/httprequest/session_spec.rb +63 -3
  39. data/spec/strelka/session/db_spec.rb +85 -0
  40. data/spec/strelka/session/default_spec.rb +5 -51
  41. data.tar.gz.sig +0 -0
  42. metadata +88 -57
  43. metadata.gz.sig +0 -0
@@ -0,0 +1,134 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+
4
+ require 'configurability'
5
+
6
+ require 'strelka' unless defined?( Strelka )
7
+ require 'strelka/app' unless defined?( Strelka::App )
8
+ require 'strelka/authprovider'
9
+ require 'strelka/mixins'
10
+
11
+ # HTTP Basic AuthProvider class -- a base class for RFC2617 Basic HTTP Authentication
12
+ # providers for {the Streka :auth plugin}[rdoc-ref:Strelka::App::Auth].
13
+ #
14
+ # == Configuration
15
+ #
16
+ # The configuration for this provider is read from the 'auth' section of the config, and
17
+ # may contain the following keys:
18
+ #
19
+ # [realm]:: the HTTP Basic realm. Defaults to the app's application ID
20
+ # [users]:: a Hash of username: SHA1+Base64'ed passwords
21
+ #
22
+ # An example:
23
+ #
24
+ # --
25
+ # auth:
26
+ # realm: Acme Admin Console
27
+ # users:
28
+ # mgranger: "9d5lIumnMJXmVT/34QrMuyj+p0E="
29
+ # jblack: "1pAnQNSVtpL1z88QwXV4sG8NMP8="
30
+ # kmurgen: "MZj9+VhZ8C9+aJhmwp+kWBL76Vs="
31
+ #
32
+ class Strelka::AuthProvider::Basic < Strelka::AuthProvider
33
+ extend Configurability,
34
+ Strelka::MethodUtilities
35
+ include Strelka::Constants,
36
+ Strelka::Loggable
37
+
38
+ # Configurability API - set the section of the config
39
+ config_key :auth
40
+
41
+
42
+ @users = nil
43
+ @realm = nil
44
+
45
+ ##
46
+ # The Hash of users and their SHA1+Base64'ed passwords
47
+ singleton_attr_accessor :users
48
+
49
+ ##
50
+ # The authentication realm
51
+ singleton_attr_accessor :realm
52
+
53
+
54
+ ### Configurability API -- configure the auth provider instance.
55
+ def self::configure( config=nil )
56
+ if config
57
+ Strelka.log.debug "Configuring Basic authprovider: %p" % [ config ]
58
+ self.realm = config['realm'] if config['realm']
59
+ self.users = config['users'] if config['users']
60
+ else
61
+ self.realm = nil
62
+ self.users = {}
63
+ end
64
+ end
65
+
66
+
67
+ #################################################################
68
+ ### I N S T A N C E M E T H O D S
69
+ #################################################################
70
+
71
+ ### Create a new Default AuthProvider.
72
+ def initialize( * )
73
+ super
74
+
75
+ # Default the authentication realm to the application's ID
76
+ unless self.class.realm
77
+ self.log.warn "No realm configured -- using the app id"
78
+ self.class.realm = self.app.conn.app_id
79
+ end
80
+
81
+ unless self.class.users
82
+ self.log.warn "No users configured -- using an empty user list"
83
+ self.class.users = {}
84
+ end
85
+ end
86
+
87
+
88
+ ######
89
+ public
90
+ ######
91
+
92
+ # Check the authentication present in +request+ (if any) for validity, returning the
93
+ # authenticating user's name if authentication succeeds.
94
+ def authenticate( request )
95
+ authheader = request.header.authorization or
96
+ self.log_failure "No authorization header in the request."
97
+
98
+ # Extract the credentials bit
99
+ base64_userpass = authheader[ /^\s*Basic\s+(\S+)$/i, 1 ] or
100
+ self.log_failure "Invalid Basic Authorization header (%p)" % [ authheader ]
101
+
102
+ # Unpack the username and password
103
+ credentials = base64_userpass.unpack( 'm' ).first
104
+ self.log_failure "Malformed credentials %p" % [ credentials ] unless
105
+ credentials.index(':')
106
+
107
+ # Split the credentials, check for valid user
108
+ username, password = credentials.split( ':', 2 )
109
+ digest = self.class.users[ username ] or
110
+ self.log_failure "No such user %p." % [ username ]
111
+
112
+ # Fail if the password's hash doesn't match
113
+ self.log_failure "Password mismatch." unless
114
+ digest == Digest::SHA1.base64digest( password )
115
+
116
+ # Success!
117
+ self.log.info "Authentication for %p succeeded." % [ username ]
118
+ return username
119
+ end
120
+
121
+
122
+ #########
123
+ protected
124
+ #########
125
+
126
+ ### Syntax sugar to allow returning 'false' while logging a reason for doing so.
127
+ ### Log a message at 'info' level and return false.
128
+ def log_failure( reason )
129
+ self.log.warn "Auth failure: %s" % [ reason ]
130
+ header = "Basic realm=%s" % [ self.class.realm ]
131
+ finish_with( HTTP::AUTH_REQUIRED, "Requires authentication.", www_authenticate: header )
132
+ end
133
+
134
+ end # class Strelka::AuthProvider::Basic
@@ -0,0 +1,91 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+
4
+ require 'ipaddr'
5
+ require 'configurability'
6
+
7
+ require 'strelka' unless defined?( Strelka )
8
+ require 'strelka/app' unless defined?( Strelka::App )
9
+ require 'strelka/authprovider'
10
+ require 'strelka/mixins'
11
+
12
+ # HostAccess AuthProvider class -- restricts access to requests coming from a list of
13
+ # netblocks.
14
+ #
15
+ # You can configure which ones from the +auth+ section of the config:
16
+ #
17
+ # auth:
18
+ # allowed_netblocks:
19
+ # - 127.0.0.0/8
20
+ # - 10.5.3.0/22
21
+ class Strelka::AuthProvider::HostAccess < Strelka::AuthProvider
22
+ include Configurability,
23
+ Strelka::Constants,
24
+ Strelka::Loggable,
25
+ Strelka::MethodUtilities
26
+
27
+
28
+ # The default list of netblocks to allow
29
+ DEFAULT_ALLOWED_NETBLOCKS = %w[127.0.0.0/8]
30
+
31
+
32
+ #################################################################
33
+ ### I N S T A N C E M E T H O D S
34
+ #################################################################
35
+
36
+ ### Create a new Default AuthProvider.
37
+ def initialize( * )
38
+ super
39
+
40
+ self.allowed_netblocks = DEFAULT_ALLOWED_NETBLOCKS
41
+
42
+ # Register this instance with Configurability
43
+ config_key :auth
44
+ end
45
+
46
+
47
+ ######
48
+ public
49
+ ######
50
+
51
+ # An Array of IPAddr objects that represent the netblocks that will be allowed
52
+ # access to the protected resources
53
+ attr_reader :allowed_netblocks
54
+
55
+
56
+ ### Set the list of allowed netblocks to +newblocks+.
57
+ def allowed_netblocks=( newblocks )
58
+ @allowed_netblocks = Array( newblocks ).map {|addr| IPAddr.new(addr) }
59
+ end
60
+
61
+
62
+ ### Configurability API -- configure the auth provider instance.
63
+ def configure( config=nil )
64
+ self.log.debug "Configuring %p with config: %p" % [ self, config ]
65
+ if config && config['allowed_netblocks']
66
+ self.allowed_netblocks = config['allowed_netblocks']
67
+ else
68
+ self.allowed_netblocks = DEFAULT_ALLOWED_NETBLOCKS
69
+ end
70
+ end
71
+
72
+
73
+ ### Check authorization for the specified +request+ by testing its the IP in its
74
+ ### X-forwarded-for header against the allowed_netblocks.
75
+ def authorize( _, request )
76
+ client_ip = request.header.x_forwarded_for or
77
+ raise "No X-Forwarded-For header?!"
78
+ addr = IPAddr.new( client_ip )
79
+
80
+ return true if self.in_allowed_netblocks?( addr )
81
+
82
+ return false
83
+ end
84
+
85
+
86
+ ### Returns +true+ if the given +ipaddr+ is in the #allowed_netblocks.
87
+ def in_allowed_netblocks?( ipaddr )
88
+ return self.allowed_netblocks.any? {|nb| nb.include?(ipaddr) }
89
+ end
90
+
91
+ end # class Strelka::AuthProvider::HostAccess
@@ -0,0 +1,122 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'pluginfactory'
6
+
7
+ require 'strelka' unless defined?( Strelka )
8
+ require 'strelka/mixins'
9
+
10
+
11
+ # This is the abstract base class for authentication and/or authorization providers
12
+ # for the {:auth plugin}[Strelka::App::Auth].
13
+ #
14
+ # To define your own session type, you'll need to inherit this class (either
15
+ # directly or via a subclass), name it <tt>Strelka::AuthProvider::{Something}</tt>,
16
+ # save it in a file named <tt>strelka/authprovider/{something}.rb</tt>, and
17
+ # override the required methods.
18
+ #
19
+ # Which methods you'll need to provide implementations for depends on whether
20
+ # your provider provides *authentication*, *authorization*, or both.
21
+ #
22
+ # == Authentication Providers
23
+ #
24
+ # Authentication providers should override either one or both of the following methods,
25
+ # depending on whether they will provide
26
+ #
27
+ # * #[]
28
+ # * #[]=
29
+ # * #save
30
+ # * #delete
31
+ # * #key?
32
+ # * #namespace=
33
+ # * #namespace
34
+ #
35
+ # These methods provide basic functionality, but you might find it more efficient
36
+ # to override them:
37
+ #
38
+ # * self.load_or_create
39
+ # * self.load
40
+ #
41
+ #
42
+ class Strelka::AuthProvider
43
+ extend Strelka::Delegation
44
+ include PluginFactory,
45
+ Strelka::Loggable,
46
+ Strelka::Constants,
47
+ Strelka::AbstractClass
48
+
49
+
50
+ ### PluginFactory API -- return the Array of directories to search for concrete
51
+ ### AuthProvider classes.
52
+ def self::derivative_dirs
53
+ return ['strelka/authprovider']
54
+ end
55
+
56
+
57
+ #################################################################
58
+ ### I N S T A N C E M E T H O D S
59
+ #################################################################
60
+
61
+ ### Create a new AuthProvider for the given +app+.
62
+ def initialize( app )
63
+ @app = app
64
+ end
65
+
66
+
67
+ ######
68
+ public
69
+ ######
70
+
71
+ ##
72
+ # The Strelka::App that the AuthProvider belongs to.
73
+ attr_reader :app
74
+
75
+
76
+ ### You should override this method if you want to authenticate the +request+. It should
77
+ ### return a credentials object if authentication is successful, or throw an auth_required
78
+ ### response if it fails.
79
+ def authenticate( request )
80
+ self.log.debug "No authentication provided, returning anonymous credentials."
81
+ return 'anonymous'
82
+ end
83
+
84
+
85
+ ### If the +callback+ is set, call it with the specified +credentials+, and +request. Override this in
86
+ ### your own AuthProvider to provide +additional_arguments+ to the +callback+, and/or to provide
87
+ ### additional generic authorization.
88
+ def authorize( credentials, request, *additional_arguments, &callback )
89
+ return true unless callback
90
+ return true if callback.call( credentials, request, *additional_arguments )
91
+ self.require_authorization
92
+ end
93
+
94
+
95
+ #########
96
+ protected
97
+ #########
98
+
99
+ ### Throw a 401 (Unauthorized) response with the specified +challenge+ as the
100
+ ### www-Authenticate header.
101
+ def require_authentication( challenge )
102
+ finish_with( HTTP::AUTH_REQUIRED, "Requires authentication.", www_authenticate: challenge )
103
+ end
104
+
105
+
106
+ ### Throw a 403 (Forbidden) response with the specified +message+.
107
+ def require_authorization( message="You are not authorized to access this resource." )
108
+ finish_with( HTTP::FORBIDDEN, message )
109
+ end
110
+
111
+
112
+ ### Abort the current execution and return a response with the specified
113
+ ### http_status code immediately. The specified +message+ will be logged,
114
+ ### and will be included in any message that is returned as part of the
115
+ ### response. The +headers+ hash will be used to set response headers.
116
+ def finish_with( http_status, message, headers={} )
117
+ status_info = { :status => http_status, :message => message, :headers => headers }
118
+ throw :finish, status_info
119
+ end
120
+
121
+ end # class Strelka::AuthProvider
122
+
@@ -24,7 +24,7 @@ class Strelka::Cookie
24
24
  include Strelka::Loggable
25
25
 
26
26
  # The format of the date field
27
- COOKIE_DATE_FORMAT = '%a, %d-%b-%Y %H:%M:%S GMT'
27
+ COOKIE_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
28
28
 
29
29
  ### RFC 2109: HTTP State Management Mechanism
30
30
  # When it sends a request to an origin server, the user agent sends a
@@ -82,7 +82,7 @@ class Strelka::CookieSet
82
82
 
83
83
 
84
84
  ### Index set operator method: set the cookie that corresponds to the given +name+
85
- ### to +value+. If +value+ is not an Strelka::Cookie, one with be created and its
85
+ ### to +value+. If +value+ is not an Strelka::Cookie, one is created and its
86
86
  ### value set to +value+.
87
87
  def []=( name, value )
88
88
  value = Strelka::Cookie.new( name.to_s, value ) unless value.is_a?( Strelka::Cookie )
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'strelka/constants'
4
+ require 'strelka/httprequest' unless defined?( Strelka::HTTPRequest )
5
+
6
+
7
+ # The mixin that adds methods to Strelka::HTTPRequest for
8
+ # authentication/authorization.
9
+ module Strelka::HTTPRequest::Auth
10
+ include Strelka::Constants
11
+
12
+
13
+ ### Extension callback -- add instance variables to extended objects.
14
+ def initialize( * )
15
+ super
16
+ @authenticated_user = nil
17
+ end
18
+
19
+
20
+ ######
21
+ public
22
+ ######
23
+
24
+ # The current session namespace
25
+ attr_accessor :authenticated_user
26
+ alias_method :authenticated?, :authenticated_user
27
+
28
+
29
+ end # module Strelka::HTTPRequest::Auth
30
+
31
+
@@ -117,12 +117,68 @@ module Strelka::Logging
117
117
  return self.format % args
118
118
  end
119
119
  end
120
- end # class LogFormatter
120
+ end # class Formatter
121
121
 
122
122
 
123
123
  # A ANSI-colorized formatter for Logger instances.
124
124
  class ColorFormatter < Logger::Formatter
125
- extend Strelka::ANSIColorUtilities
125
+
126
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
127
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
128
+ ANSI_ATTRIBUTES = {
129
+ 'clear' => 0,
130
+ 'reset' => 0,
131
+ 'bold' => 1,
132
+ 'dark' => 2,
133
+ 'underline' => 4,
134
+ 'underscore' => 4,
135
+ 'blink' => 5,
136
+ 'reverse' => 7,
137
+ 'concealed' => 8,
138
+
139
+ 'black' => 30, 'on_black' => 40,
140
+ 'red' => 31, 'on_red' => 41,
141
+ 'green' => 32, 'on_green' => 42,
142
+ 'yellow' => 33, 'on_yellow' => 43,
143
+ 'blue' => 34, 'on_blue' => 44,
144
+ 'magenta' => 35, 'on_magenta' => 45,
145
+ 'cyan' => 36, 'on_cyan' => 46,
146
+ 'white' => 37, 'on_white' => 47
147
+ }
148
+
149
+
150
+ ### Create a string that contains the ANSI codes specified and return it
151
+ def self::ansi_code( *attributes )
152
+ attributes.flatten!
153
+ attributes.collect! {|at| at.to_s }
154
+ return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
155
+ attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
156
+
157
+ if attributes.empty?
158
+ return ''
159
+ else
160
+ return "\e[%sm" % attributes
161
+ end
162
+ end
163
+
164
+
165
+ ### Colorize the given +string+ with the specified +attributes+ and
166
+ ### return it, handling line-endings, color reset, etc.
167
+ def self::colorize( *args )
168
+ string = ''
169
+
170
+ if block_given?
171
+ string = yield
172
+ else
173
+ string = args.shift
174
+ end
175
+
176
+ ending = string[/(\s)$/] || ''
177
+ string = string.rstrip
178
+
179
+ return self.ansi_code( args.flatten ) + string + self.ansi_code( 'reset' ) + ending
180
+ end
181
+
126
182
 
127
183
  # Color settings
128
184
  LEVEL_FORMATS = {
@@ -169,7 +225,8 @@ module Strelka::Logging
169
225
 
170
226
  return self.settings[ severity.downcase.to_sym ] % args
171
227
  end
172
- end # class LogFormatter
228
+
229
+ end # class Formatter
173
230
 
174
231
 
175
232
  # An alternate formatter for Logger instances that outputs +div+ HTML
@@ -216,9 +273,9 @@ module Strelka::Logging
216
273
  time.usec, # %2$d
217
274
  Process.pid, # %3$d
218
275
  Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
219
- severity.downcase, # %5$s
276
+ severity.downcase, # %5$s
220
277
  progname, # %6$s
221
- html_escape( msg ).gsub(/\n/, '<br />') # %7$s
278
+ escape_html( msg ).gsub(/\n/, '<br />') # %7$s
222
279
  ]
223
280
 
224
281
  return self.format % args
@@ -229,18 +286,16 @@ module Strelka::Logging
229
286
  private
230
287
  #######
231
288
 
232
- ### Return a copy of the specified +string+ with HTML special characters escaped as
233
- ### HTML entities.
234
- def html_escape( string )
289
+ ### Escape any HTML special characters in +string+.
290
+ def escape_html( string )
235
291
  return string.
236
- gsub( /&/, '&amp;' ).
237
- gsub( /</, '&lt;' ).
238
- gsub( />/, '&gt;' )
292
+ gsub( '&', '&amp;' ).
293
+ gsub( '<', '&lt;' ).
294
+ gsub( '>', '&gt;' )
239
295
  end
240
296
 
241
- end # class HtmlLogFormatter
297
+ end # class HtmlFormatter
242
298
 
243
- end # module Strelka
244
299
 
245
- # vim: set nosta noet ts=4 sw=4:
300
+ end # module Strelka
246
301
 
@@ -10,7 +10,17 @@ require 'strelka/constants'
10
10
 
11
11
  module Strelka
12
12
 
13
- # Add logging to a Strelka class. Including classes get #log and #log_debug methods.
13
+ # Add logging to a Strelka class. Including classes get #log and
14
+ # #log_debug methods.
15
+ #
16
+ # class MyClass
17
+ # include Inversion::Loggable
18
+ #
19
+ # def a_method
20
+ # self.log.debug "Doing a_method stuff..."
21
+ # end
22
+ # end
23
+ #
14
24
  module Loggable
15
25
 
16
26
  # A logging proxy class that wraps calls to the logger into calls that include
@@ -75,70 +85,6 @@ module Strelka
75
85
 
76
86
  end # module Loggable
77
87
 
78
- # A collection of ANSI color utility functions
79
- module ANSIColorUtilities
80
-
81
- # Set some ANSI escape code constants (Shamelessly stolen from Perl's
82
- # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
83
- ANSI_ATTRIBUTES = {
84
- 'clear' => 0,
85
- 'reset' => 0,
86
- 'bold' => 1,
87
- 'dark' => 2,
88
- 'underline' => 4,
89
- 'underscore' => 4,
90
- 'blink' => 5,
91
- 'reverse' => 7,
92
- 'concealed' => 8,
93
-
94
- 'black' => 30, 'on_black' => 40,
95
- 'red' => 31, 'on_red' => 41,
96
- 'green' => 32, 'on_green' => 42,
97
- 'yellow' => 33, 'on_yellow' => 43,
98
- 'blue' => 34, 'on_blue' => 44,
99
- 'magenta' => 35, 'on_magenta' => 45,
100
- 'cyan' => 36, 'on_cyan' => 46,
101
- 'white' => 37, 'on_white' => 47
102
- }
103
-
104
- ###############
105
- module_function
106
- ###############
107
-
108
- ### Create a string that contains the ANSI codes specified and return it
109
- def ansi_code( *attributes )
110
- attributes.flatten!
111
- attributes.collect! {|at| at.to_s }
112
- return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
113
- attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
114
-
115
- if attributes.empty?
116
- return ''
117
- else
118
- return "\e[%sm" % attributes
119
- end
120
- end
121
-
122
-
123
- ### Colorize the given +string+ with the specified +attributes+ and return it, handling
124
- ### line-endings, color reset, etc.
125
- def colorize( *args )
126
- string = ''
127
-
128
- if block_given?
129
- string = yield
130
- else
131
- string = args.shift
132
- end
133
-
134
- ending = string[/(\s)$/] || ''
135
- string = string.rstrip
136
-
137
- return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
138
- end
139
-
140
- end # module ANSIColorUtilities
141
-
142
88
 
143
89
  # Hides your class's ::new method and adds a +pure_virtual+ method generator for
144
90
  # defining API methods. If subclasses of your class don't provide implementations of
@@ -244,6 +190,17 @@ module Strelka
244
190
  end
245
191
 
246
192
 
193
+ ### Define the given +delegated_methods+ as delegators to the like-named class
194
+ ### method.
195
+ def def_class_delegators( *delegated_methods )
196
+ delegated_methods.each do |name|
197
+ define_method( name ) do |*args|
198
+ self.class.__send__( name, *args )
199
+ end
200
+ end
201
+ end
202
+
203
+
247
204
  #######
248
205
  private
249
206
  #######
@@ -296,6 +253,10 @@ module Strelka
296
253
 
297
254
  # A collection of miscellaneous functions that are useful for manipulating
298
255
  # complex data structures.
256
+ #
257
+ # include Strelka::DataUtilities
258
+ # newhash = deep_copy( oldhash )
259
+ #
299
260
  module DataUtilities
300
261
 
301
262
  ###############
@@ -331,6 +292,15 @@ module Strelka
331
292
 
332
293
 
333
294
  # A collection of methods for declaring other methods.
295
+ #
296
+ # class MyClass
297
+ # include Strelka::MethodUtilities
298
+ #
299
+ # singleton_attr_accessor :types
300
+ # end
301
+ #
302
+ # MyClass.types = [ :pheno, :proto, :stereo ]
303
+ #
334
304
  module MethodUtilities
335
305
 
336
306
  ### Creates instance variables and corresponding methods that return their