treequel 1.0.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 (74) hide show
  1. data/ChangeLog +354 -0
  2. data/LICENSE +27 -0
  3. data/README +66 -0
  4. data/Rakefile +345 -0
  5. data/Rakefile.local +43 -0
  6. data/bin/treeirb +14 -0
  7. data/bin/treequel +229 -0
  8. data/examples/company-directory.rb +112 -0
  9. data/examples/ldap-monitor.rb +143 -0
  10. data/examples/ldap-monitor/public/css/master.css +328 -0
  11. data/examples/ldap-monitor/public/images/card_small.png +0 -0
  12. data/examples/ldap-monitor/public/images/chain_small.png +0 -0
  13. data/examples/ldap-monitor/public/images/globe_small.png +0 -0
  14. data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
  15. data/examples/ldap-monitor/public/images/plug.png +0 -0
  16. data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
  17. data/examples/ldap-monitor/public/images/tick.png +0 -0
  18. data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
  19. data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
  20. data/examples/ldap-monitor/views/backends.erb +41 -0
  21. data/examples/ldap-monitor/views/connections.erb +74 -0
  22. data/examples/ldap-monitor/views/databases.erb +39 -0
  23. data/examples/ldap-monitor/views/dump_subsystem.erb +14 -0
  24. data/examples/ldap-monitor/views/index.erb +14 -0
  25. data/examples/ldap-monitor/views/layout.erb +35 -0
  26. data/examples/ldap-monitor/views/listeners.erb +30 -0
  27. data/examples/ldap_state.rb +62 -0
  28. data/lib/treequel.rb +145 -0
  29. data/lib/treequel/branch.rb +589 -0
  30. data/lib/treequel/branchcollection.rb +204 -0
  31. data/lib/treequel/branchset.rb +360 -0
  32. data/lib/treequel/constants.rb +604 -0
  33. data/lib/treequel/directory.rb +541 -0
  34. data/lib/treequel/exceptions.rb +32 -0
  35. data/lib/treequel/filter.rb +704 -0
  36. data/lib/treequel/mixins.rb +325 -0
  37. data/lib/treequel/schema.rb +245 -0
  38. data/lib/treequel/schema/attributetype.rb +252 -0
  39. data/lib/treequel/schema/ldapsyntax.rb +96 -0
  40. data/lib/treequel/schema/matchingrule.rb +124 -0
  41. data/lib/treequel/schema/matchingruleuse.rb +124 -0
  42. data/lib/treequel/schema/objectclass.rb +289 -0
  43. data/lib/treequel/sequel_integration.rb +26 -0
  44. data/lib/treequel/utils.rb +169 -0
  45. data/rake/191_compat.rb +26 -0
  46. data/rake/dependencies.rb +76 -0
  47. data/rake/helpers.rb +434 -0
  48. data/rake/hg.rb +261 -0
  49. data/rake/manual.rb +782 -0
  50. data/rake/packaging.rb +135 -0
  51. data/rake/publishing.rb +318 -0
  52. data/rake/rdoc.rb +30 -0
  53. data/rake/style.rb +62 -0
  54. data/rake/svn.rb +668 -0
  55. data/rake/testing.rb +187 -0
  56. data/rake/verifytask.rb +64 -0
  57. data/rake/win32.rb +190 -0
  58. data/spec/lib/constants.rb +93 -0
  59. data/spec/lib/helpers.rb +100 -0
  60. data/spec/treequel/branch_spec.rb +569 -0
  61. data/spec/treequel/branchcollection_spec.rb +213 -0
  62. data/spec/treequel/branchset_spec.rb +376 -0
  63. data/spec/treequel/directory_spec.rb +487 -0
  64. data/spec/treequel/filter_spec.rb +482 -0
  65. data/spec/treequel/mixins_spec.rb +330 -0
  66. data/spec/treequel/schema/attributetype_spec.rb +237 -0
  67. data/spec/treequel/schema/ldapsyntax_spec.rb +83 -0
  68. data/spec/treequel/schema/matchingrule_spec.rb +158 -0
  69. data/spec/treequel/schema/matchingruleuse_spec.rb +137 -0
  70. data/spec/treequel/schema/objectclass_spec.rb +262 -0
  71. data/spec/treequel/schema_spec.rb +118 -0
  72. data/spec/treequel/utils_spec.rb +49 -0
  73. data/spec/treequel_spec.rb +179 -0
  74. metadata +169 -0
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rbconfig'
4
+ require 'erb'
5
+ require 'etc'
6
+ require 'logger'
7
+
8
+ require 'treequel'
9
+
10
+
11
+ #--
12
+ # A collection of mixins shared between Treequel classes. Stolen mostly from ThingFish.
13
+ #
14
+ module Treequel # :nodoc:
15
+
16
+ # A collection of various delegation code-generators that can be used to define
17
+ # delegation through other methods, to instance variables, etc.
18
+ module Delegation
19
+
20
+ #######
21
+ private
22
+ #######
23
+
24
+ ### Make the body of a delegator method that will delegate to the +name+ method
25
+ ### of the object returned by the +delegate+ method.
26
+ def make_method_delegator( delegate, name )
27
+ error_frame = caller(4)[0]
28
+ file, line = error_frame.split( ':', 2 )
29
+
30
+ # Ruby can't parse obj.method=(*args), so we have to special-case setters...
31
+ if name.to_s =~ /(\w+)=$/
32
+ name = $1
33
+ code = <<-END_CODE
34
+ lambda {|*args| self.#{delegate}.#{name} = *args }
35
+ END_CODE
36
+ else
37
+ code = <<-END_CODE
38
+ lambda {|*args| self.#{delegate}.#{name}(*args) }
39
+ END_CODE
40
+ end
41
+
42
+ return eval( code, nil, file, line.to_i )
43
+ end
44
+
45
+
46
+ ### Make the body of a delegator method that will delegate calls to the +name+
47
+ ### method to the given +ivar+.
48
+ def make_ivar_delegator( ivar, name )
49
+ error_frame = caller(4)[0]
50
+ file, line = error_frame.split( ':', 2 )
51
+
52
+ # Ruby can't parse obj.method=(*args), so we have to special-case setters...
53
+ if name.to_s =~ /(\w+)=$/
54
+ name = $1
55
+ code = <<-END_CODE
56
+ lambda {|*args| #{ivar}.#{name} = *args }
57
+ END_CODE
58
+ else
59
+ code = <<-END_CODE
60
+ lambda {|*args| #{ivar}.#{name}(*args) }
61
+ END_CODE
62
+ end
63
+
64
+ return eval( code, nil, file, line.to_i )
65
+ end
66
+
67
+
68
+ ###############
69
+ module_function
70
+ ###############
71
+
72
+ ### Define the given +delegated_methods+ as delegators to the like-named method
73
+ ### of the return value of the +delegate_method+. Example:
74
+ ###
75
+ ### class MyClass
76
+ ### extend Treequel::Delegation
77
+ ###
78
+ ### # Delegate the #bound?, #err, and #result2error methods to the connection
79
+ ### # object returned by the #connection method. This allows the connection
80
+ ### # to still be loaded on demand/overridden/etc.
81
+ ### def_method_delegators :connection, :bound?, :err, :result2error
82
+ ###
83
+ ### def connection
84
+ ### @connection ||= self.connect
85
+ ### end
86
+ ### end
87
+ ###
88
+ def def_method_delegators( delegate_method, *delegated_methods )
89
+ delegated_methods.each do |name|
90
+ body = make_method_delegator( delegate_method, name )
91
+ define_method( name, &body )
92
+ end
93
+ end
94
+
95
+
96
+ ### Define the given +delegated_methods+ as delegators to the like-named method
97
+ ### of the specified +ivar+. This is pretty much identical with how 'Forwardable'
98
+ ### from the stdlib does delegation, but it's reimplemented here for consistency.
99
+ ###
100
+ ### class MyClass
101
+ ### extend Treequel::Delegation
102
+ ###
103
+ ### # Delegate the #each method to the @collection ivar
104
+ ### def_ivar_delegators :@collection, :each
105
+ ###
106
+ ### end
107
+ ###
108
+ def def_ivar_delegators( ivar, *delegated_methods )
109
+ delegated_methods.each do |name|
110
+ body = make_ivar_delegator( ivar, name )
111
+ define_method( name, &body )
112
+ end
113
+ end
114
+
115
+
116
+ end # module Delegation
117
+
118
+
119
+ #
120
+ # Add logging to a Treequel class. Including classes get #log and #log_debug methods.
121
+ #
122
+ # == Version
123
+ #
124
+ # $Id$
125
+ #
126
+ # == Authors
127
+ #
128
+ # * Michael Granger <ged@FaerieMUD.org>
129
+ #
130
+ # :include: LICENSE
131
+ #
132
+ # --
133
+ #
134
+ # Please see the file LICENSE in the 'docs' directory for licensing details.
135
+ #
136
+ module Loggable
137
+
138
+ LEVEL = {
139
+ :debug => Logger::DEBUG,
140
+ :info => Logger::INFO,
141
+ :warn => Logger::WARN,
142
+ :error => Logger::ERROR,
143
+ :fatal => Logger::FATAL,
144
+ }
145
+
146
+ ### A logging proxy class that wraps calls to the logger into calls that include
147
+ ### the name of the calling class.
148
+ class ClassNameProxy # :nodoc:
149
+
150
+ ### Create a new proxy for the given +klass+.
151
+ def initialize( klass, force_debug=false )
152
+ @classname = klass.name
153
+ @force_debug = force_debug
154
+ end
155
+
156
+ ### Delegate calls the global logger with the class name as the 'progname'
157
+ ### argument.
158
+ def method_missing( sym, msg=nil, &block )
159
+ return super unless LEVEL.key?( sym )
160
+ sym = :debug if @force_debug
161
+ Treequel.logger.add( LEVEL[sym], msg, @classname, &block )
162
+ end
163
+ end # ClassNameProxy
164
+
165
+ #########
166
+ protected
167
+ #########
168
+
169
+ ### Copy constructor -- clear the original's log proxy.
170
+ def initialize_copy( original )
171
+ @log_proxy = @log_debug_proxy = nil
172
+ super
173
+ end
174
+
175
+ ### Return the proxied logger.
176
+ def log
177
+ @log_proxy ||= ClassNameProxy.new( self.class )
178
+ end
179
+
180
+ ### Return a proxied "debug" logger that ignores other level specification.
181
+ def log_debug
182
+ @log_debug_proxy ||= ClassNameProxy.new( self.class, true )
183
+ end
184
+ end # module Loggable
185
+
186
+
187
+ ### A collection of utilities for working with Hashes.
188
+ module HashUtilities
189
+
190
+ ###############
191
+ module_function
192
+ ###############
193
+
194
+ ### Return a version of the given +hash+ with its keys transformed
195
+ ### into Strings from whatever they were before.
196
+ def stringify_keys( hash )
197
+ newhash = {}
198
+
199
+ hash.each do |key,val|
200
+ if val.is_a?( Hash )
201
+ newhash[ key.to_s ] = stringify_keys( val )
202
+ else
203
+ newhash[ key.to_s ] = val
204
+ end
205
+ end
206
+
207
+ return newhash
208
+ end
209
+
210
+
211
+ ### Return a duplicate of the given +hash+ with its identifier-like keys
212
+ ### transformed into symbols from whatever they were before.
213
+ def symbolify_keys( hash )
214
+ newhash = {}
215
+
216
+ hash.each do |key,val|
217
+ keysym = key.to_s.dup.untaint.to_sym
218
+
219
+ if val.is_a?( Hash )
220
+ newhash[ keysym ] = symbolify_keys( val )
221
+ else
222
+ newhash[ keysym ] = val
223
+ end
224
+ end
225
+
226
+ return newhash
227
+ end
228
+ alias_method :internify_keys, :symbolify_keys
229
+
230
+
231
+ # Recursive hash-merge function
232
+ def merge_recursively( key, oldval, newval )
233
+ case oldval
234
+ when Hash
235
+ case newval
236
+ when Hash
237
+ oldval.merge( newval, &method(:merge_recursively) )
238
+ else
239
+ newval
240
+ end
241
+
242
+ when Array
243
+ case newval
244
+ when Array
245
+ oldval | newval
246
+ else
247
+ newval
248
+ end
249
+
250
+ else
251
+ newval
252
+ end
253
+ end
254
+
255
+ end # HashUtilities
256
+
257
+
258
+ ### A collection of utilities for working with Arrays.
259
+ module ArrayUtilities
260
+
261
+ ###############
262
+ module_function
263
+ ###############
264
+
265
+ ### Return a version of the given +array+ with any Symbols contained in it turned into
266
+ ### Strings.
267
+ def stringify_array( array )
268
+ return array.collect do |item|
269
+ case item
270
+ when Symbol
271
+ item.to_s
272
+ when Array
273
+ stringify_array( item )
274
+ else
275
+ item
276
+ end
277
+ end
278
+ end
279
+
280
+
281
+ ### Return a version of the given +array+ with any Strings contained in it turned into
282
+ ### Symbols.
283
+ def symbolify_array( array )
284
+ return array.collect do |item|
285
+ case item
286
+ when String
287
+ item.to_sym
288
+ when Array
289
+ symbolify_array( item )
290
+ else
291
+ item
292
+ end
293
+ end
294
+ end
295
+
296
+ end # module ArrayUtilities
297
+
298
+
299
+ ### A collection of attribute declaration functions
300
+ module AttributeDeclarations
301
+
302
+ ###############
303
+ module_function
304
+ ###############
305
+
306
+ ### Declare predicate accessors for the attributes associated with the specified
307
+ ### +symbols+.
308
+ def predicate_attr( *symbols )
309
+ symbols.each do |attrname|
310
+ define_method( "#{attrname}?" ) do
311
+ instance_variable_get( "@#{attrname}" ) ? true : false
312
+ end
313
+ define_method( "#{attrname}=" ) do |newval|
314
+ instance_variable_set( "@#{attrname}", newval ? true : false )
315
+ end
316
+ alias_method "is_#{attrname}?", "#{attrname}?"
317
+ end
318
+ end
319
+
320
+ end # module AttributeDeclarations
321
+
322
+ end # module Treequel
323
+
324
+ # vim: set nosta noet ts=4 sw=4:
325
+
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ldap'
4
+ require 'ldap/schema'
5
+
6
+ require 'treequel'
7
+ require 'treequel/constants'
8
+ require 'treequel/mixins'
9
+
10
+
11
+ # This is an object that is used to parse and query a directory's schema
12
+ #
13
+ # == Authors
14
+ #
15
+ # * Michael Granger <ged@FaerieMUD.org>
16
+ # * Mahlon E. Smith <mahlon@martini.nu>
17
+ #
18
+ # :include: LICENSE
19
+ #
20
+ #--
21
+ #
22
+ # Please see the file LICENSE in the base directory for licensing details.
23
+ #
24
+ class Treequel::Schema
25
+ include Treequel::Loggable,
26
+ Treequel::Constants::Patterns
27
+
28
+ require 'treequel/schema/objectclass'
29
+ require 'treequel/schema/attributetype'
30
+ require 'treequel/schema/matchingrule'
31
+ require 'treequel/schema/matchingruleuse'
32
+ require 'treequel/schema/ldapsyntax'
33
+
34
+
35
+ #################################################################
36
+ ### C L A S S M E T H O D S
37
+ #################################################################
38
+
39
+ ### Parse the given +oidstring+ into an Array of OIDs, with Strings for numeric OIDs and
40
+ ### Symbols for aliases.
41
+ def self::parse_oids( oidstring )
42
+ return [] unless oidstring
43
+
44
+ unless /^ #{OIDS} $/x.match( oidstring.strip )
45
+ raise Treequel::ParseError, "couldn't find an OIDLIST in %p" % [ oidstring ]
46
+ end
47
+
48
+ oids = $MATCH
49
+ # Treequel.logger.debug " found OIDs: %p" % [ oids ]
50
+
51
+ # If it's an OIDLIST, strip off leading and trailing parens and whitespace, then split
52
+ # on ' $ ' and parse each OID
53
+ if oids.include?( '$' )
54
+ parse_oid = self.method( :parse_oid )
55
+ return $MATCH[1..-2].strip.split( /#{WSP} #{DOLLAR} #{WSP}/x ).collect( &parse_oid )
56
+
57
+ else
58
+ return [ self.parse_oid(oids) ]
59
+
60
+ end
61
+
62
+ end
63
+
64
+
65
+ ### Parse a single OID into either a numeric OID string or a Symbol.
66
+ def self::parse_oid( oidstring )
67
+ if oidstring =~ NUMERICOID
68
+ return oidstring.untaint
69
+ else
70
+ return oidstring.untaint.to_sym
71
+ end
72
+ end
73
+
74
+
75
+ ### Parse the given short +names+ string (a 'qdescrs' in the BNF) into an Array of zero or
76
+ ### more Strings.
77
+ def self::parse_names( names )
78
+ # Treequel.logger.debug " parsing NAME attribute from: %p" % [ names ]
79
+
80
+ # Unspecified
81
+ if names.nil?
82
+ # Treequel.logger.debug " no NAME attribute"
83
+ return []
84
+
85
+ # Multi-value
86
+ elsif names =~ /#{LPAREN} #{WSP} (#{QDESCRLIST}) #{WSP} #{RPAREN}/x
87
+ # Treequel.logger.debug " parsing a NAME list from %p" % [ $1 ]
88
+ return $1.scan( QDESCR ).collect {|qd| qd[1..-2].untaint.to_sym }
89
+
90
+ # Single-value
91
+ else
92
+ # Return the name without the quotes
93
+ # Treequel.logger.debug " dequoting a single NAME"
94
+ return [ names[1..-2].untaint.to_sym ]
95
+ end
96
+ end
97
+
98
+
99
+ ### Return a new string which is +desc+ with quotes stripped and any escaped characters
100
+ ### un-escaped.
101
+ def self::unquote_desc( desc )
102
+ return nil if desc.nil?
103
+ return desc.gsub( QQ, "'" ).gsub( QS, '\\' )[ 1..-2 ]
104
+ end
105
+
106
+
107
+ #################################################################
108
+ ### I N S T A N C E M E T H O D S
109
+ #################################################################
110
+
111
+ ### Create a new Treequel::Schema from the specified +hash+. The +hash+ should be of the same
112
+ ### form as the one returned by LDAP::Conn.schema, i.e., a Hash of Arrays associated with the
113
+ ### keys "objectClasses", "ldapSyntaxes", "matchingRuleUse", "attributeTypes", and
114
+ ### "matchingRules".
115
+ def initialize( hash )
116
+ @object_classes = self.parse_objectclasses( hash['objectClasses'] )
117
+ @attribute_types = self.parse_attribute_types( hash['attributeTypes'] )
118
+ @ldap_syntaxes = self.parse_ldap_syntaxes( hash['ldapSyntaxes'] )
119
+ @matching_rules = self.parse_matching_rules( hash['matchingRules'] )
120
+ @matching_rule_uses = self.parse_matching_rule_uses( hash['matchingRuleUse'] )
121
+ end
122
+
123
+
124
+ ######
125
+ public
126
+ ######
127
+
128
+ # The Hash of Treequel::Schema::ObjectClass objects, keyed by OID and any associated NAME
129
+ # attributes (as Symbols), that describes the objectClasses in the directory's schema.
130
+ attr_reader :object_classes
131
+
132
+ # The hash of Treequel::Schema::AttributeType objects, keyed by OID and any associated NAME
133
+ # attributes (as Symbols), that describe the attributeTypes in the directory's schema.
134
+ attr_reader :attribute_types
135
+
136
+ # The hash of Treequel::Schema::LDAPSyntax objects, keyed by OID, that describe the
137
+ # syntaxes in the directory's schema.
138
+ attr_reader :ldap_syntaxes
139
+
140
+ # The hash of Treequel::Schema::MatchingRule objects, keyed by OID and any associated NAME
141
+ # attributes (as Symbols), that describe the matchingRules int he directory's schema.
142
+ attr_reader :matching_rules
143
+
144
+ # The hash of Treequel::Schema::MatchingRuleUse objects, keyed by OID and any associated NAME
145
+ # attributes (as Symbols), that describe the attributes to which a matchingRule can be applied.
146
+ attr_reader :matching_rule_uses
147
+
148
+
149
+ ### Return a human-readable representation of the object suitable for debugging.
150
+ def inspect
151
+ ivar_descs = self.instance_variables.sort.collect do |ivar|
152
+ len = self.instance_variable_get( ivar ).length
153
+ "%d %s" % [ len, ivar.gsub(/_/, ' ')[1..-1] ]
154
+ end
155
+ return %{#<%s:0x%0x %s>} % [
156
+ self.class.name,
157
+ self.object_id / 2,
158
+ ivar_descs.join(', '),
159
+ ]
160
+ end
161
+
162
+
163
+ #########
164
+ protected
165
+ #########
166
+
167
+ ### Parse the given objectClass +descriptions+ into Treequel::Schema::ObjectClass objects, and
168
+ ### return them as a Hash keyed both by numeric OID and by each of its NAME attributes (if it
169
+ ### has any).
170
+ def parse_objectclasses( descriptions )
171
+ return descriptions.inject( {} ) do |hash, desc|
172
+ oc = Treequel::Schema::ObjectClass.parse( self, desc ) or
173
+ raise Treequel::Error, "couldn't create an objectClass from %p" % [ desc ]
174
+
175
+ hash[ oc.oid ] = oc
176
+ oc.names.inject( hash ) {|h, name| h[name] = oc; h }
177
+
178
+ hash
179
+ end
180
+ end
181
+
182
+
183
+ ### Parse the given attributeType +descriptions+ into Treequel::Schema::AttributeType objects
184
+ ### and return them as a Hash keyed both by numeric OID and by each of its NAME attributes
185
+ ### (if it has any).
186
+ def parse_attribute_types( descriptions )
187
+ return descriptions.inject( {} ) do |hash, desc|
188
+ attrtype = Treequel::Schema::AttributeType.parse( self, desc ) or
189
+ raise Treequel::Error, "couldn't create an attributeType from %p" % [ desc ]
190
+
191
+ hash[ attrtype.oid ] = attrtype
192
+ attrtype.names.inject( hash ) {|h, name| h[name] = attrtype; h }
193
+
194
+ hash
195
+ end
196
+ end
197
+
198
+
199
+ ### Parse the given LDAP syntax +descriptions+ into Treequel::Schema::LDAPSyntax objects and
200
+ ### return them as a Hash keyed by numeric OID.
201
+ def parse_ldap_syntaxes( descriptions )
202
+ return descriptions.inject( {} ) do |hash, desc|
203
+ syntax = Treequel::Schema::LDAPSyntax.parse( self, desc ) or
204
+ raise Treequel::Error, "couldn't create an LDAPSyntax from %p" % [ desc ]
205
+
206
+ hash[ syntax.oid ] = syntax
207
+ hash
208
+ end
209
+ end
210
+
211
+
212
+ ### Parse the given matchingRule +descriptions+ into Treequel::Schema::MatchingRule objects
213
+ ### and return them as a Hash keyed both by numeric OID and by each of its NAME attributes
214
+ ### (if it has any).
215
+ def parse_matching_rules( descriptions )
216
+ return descriptions.inject( {} ) do |hash, desc|
217
+ rule = Treequel::Schema::MatchingRule.parse( self, desc ) or
218
+ raise Treequel::Error, "couldn't create an matchingRule from %p" % [ desc ]
219
+
220
+ hash[ rule.oid ] = rule
221
+ rule.names.inject( hash ) {|h, name| h[name] = rule; h }
222
+
223
+ hash
224
+ end
225
+ end
226
+
227
+
228
+ ### Parse the given matchingRuleUse +descriptions+ into Treequel::Schema::MatchingRuleUse objects
229
+ ### and return them as a Hash keyed both by numeric OID and by each of its NAME attributes
230
+ ### (if it has any).
231
+ def parse_matching_rule_uses( descriptions )
232
+ return descriptions.inject( {} ) do |hash, desc|
233
+ ruleuse = Treequel::Schema::MatchingRuleUse.parse( self, desc ) or
234
+ raise Treequel::Error, "couldn't create an matchingRuleUse from %p" % [ desc ]
235
+
236
+ hash[ ruleuse.oid ] = ruleuse
237
+ ruleuse.names.inject( hash ) {|h, name| h[name] = ruleuse; h }
238
+
239
+ hash
240
+ end
241
+ end
242
+
243
+
244
+ end # class Treequel::Schema
245
+