strelka 0.0.1.pre148 → 0.0.1.pre177

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