treequel 1.0.1 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/ChangeLog +176 -14
  2. data/LICENSE +1 -1
  3. data/Rakefile +61 -45
  4. data/Rakefile.local +20 -0
  5. data/bin/treequel +502 -269
  6. data/examples/ldap-rack-auth.rb +2 -0
  7. data/lib/treequel.rb +221 -18
  8. data/lib/treequel/branch.rb +410 -201
  9. data/lib/treequel/branchcollection.rb +25 -13
  10. data/lib/treequel/branchset.rb +42 -40
  11. data/lib/treequel/constants.rb +233 -3
  12. data/lib/treequel/control.rb +95 -0
  13. data/lib/treequel/controls/contentsync.rb +138 -0
  14. data/lib/treequel/controls/pagedresults.rb +162 -0
  15. data/lib/treequel/controls/sortedresults.rb +216 -0
  16. data/lib/treequel/directory.rb +212 -65
  17. data/lib/treequel/exceptions.rb +11 -12
  18. data/lib/treequel/filter.rb +1 -12
  19. data/lib/treequel/mixins.rb +83 -47
  20. data/lib/treequel/monkeypatches.rb +29 -0
  21. data/lib/treequel/schema.rb +23 -19
  22. data/lib/treequel/schema/attributetype.rb +33 -3
  23. data/lib/treequel/schema/ldapsyntax.rb +0 -11
  24. data/lib/treequel/schema/matchingrule.rb +0 -11
  25. data/lib/treequel/schema/matchingruleuse.rb +0 -11
  26. data/lib/treequel/schema/objectclass.rb +36 -10
  27. data/lib/treequel/schema/table.rb +159 -0
  28. data/lib/treequel/sequel_integration.rb +7 -7
  29. data/lib/treequel/utils.rb +4 -66
  30. data/rake/documentation.rb +89 -0
  31. data/rake/helpers.rb +375 -307
  32. data/rake/hg.rb +16 -2
  33. data/rake/manual.rb +11 -6
  34. data/rake/packaging.rb +20 -35
  35. data/rake/publishing.rb +22 -62
  36. data/spec/lib/constants.rb +20 -0
  37. data/spec/lib/control_behavior.rb +44 -0
  38. data/spec/lib/matchers.rb +51 -0
  39. data/spec/treequel/branch_spec.rb +88 -29
  40. data/spec/treequel/branchcollection_spec.rb +24 -1
  41. data/spec/treequel/branchset_spec.rb +123 -51
  42. data/spec/treequel/control_spec.rb +48 -0
  43. data/spec/treequel/controls/contentsync_spec.rb +38 -0
  44. data/spec/treequel/controls/pagedresults_spec.rb +138 -0
  45. data/spec/treequel/controls/sortedresults_spec.rb +171 -0
  46. data/spec/treequel/directory_spec.rb +186 -16
  47. data/spec/treequel/mixins_spec.rb +42 -3
  48. data/spec/treequel/schema/attributetype_spec.rb +22 -20
  49. data/spec/treequel/schema/objectclass_spec.rb +67 -46
  50. data/spec/treequel/schema/table_spec.rb +134 -0
  51. data/spec/treequel_spec.rb +277 -15
  52. metadata +89 -108
  53. data/bin/treequel.orig +0 -963
  54. data/examples/ldap-monitor.rb +0 -143
  55. data/examples/ldap-monitor/public/css/master.css +0 -328
  56. data/examples/ldap-monitor/public/images/card_small.png +0 -0
  57. data/examples/ldap-monitor/public/images/chain_small.png +0 -0
  58. data/examples/ldap-monitor/public/images/globe_small.png +0 -0
  59. data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
  60. data/examples/ldap-monitor/public/images/plug.png +0 -0
  61. data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
  62. data/examples/ldap-monitor/public/images/tick.png +0 -0
  63. data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
  64. data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
  65. data/examples/ldap-monitor/views/backends.erb +0 -41
  66. data/examples/ldap-monitor/views/connections.erb +0 -74
  67. data/examples/ldap-monitor/views/databases.erb +0 -39
  68. data/examples/ldap-monitor/views/dump_subsystem.erb +0 -14
  69. data/examples/ldap-monitor/views/index.erb +0 -14
  70. data/examples/ldap-monitor/views/layout.erb +0 -35
  71. data/examples/ldap-monitor/views/listeners.erb +0 -30
  72. data/rake/rdoc.rb +0 -30
  73. data/rake/win32.rb +0 -190
@@ -3,18 +3,6 @@
3
3
  require 'treequel'
4
4
 
5
5
 
6
- # A collection of exceptions for Treequel.
7
- #
8
- # == Authors
9
- #
10
- # * Michael Granger <ged@FaerieMUD.org>
11
- #
12
- # :include: LICENSE
13
- #
14
- #--
15
- #
16
- # Please see the file LICENSE in the base directory for licensing details.
17
- #
18
6
  module Treequel
19
7
 
20
8
  ### The base Treequel exception type
@@ -27,6 +15,17 @@ module Treequel
27
15
  ### arguments given to Treequel::Filter.new
28
16
  class ExpressionError < Treequel::Error; end
29
17
 
18
+ ### Generic exception type for Controls.
19
+ class ControlError < Treequel::Error; end
20
+
21
+ ### Exception type for a requested Control type that is nonexistent or
22
+ ### unsupported on the current server.
23
+ class UnsupportedControl < Treequel::ControlError; end
24
+
25
+ ### Exception raised from Treequel::Model due to misconfiguration or
26
+ ### other problem.
27
+ class ModelError < Treequel::Error; end
28
+
30
29
  end # module Treequel
31
30
 
32
31
 
@@ -36,18 +36,6 @@ require 'treequel/sequel_integration'
36
36
  # matchingrule = MatchingRuleId from Section 4.1.9 of [1]
37
37
  # value = AttributeValue from Section 4.1.6 of [1]
38
38
  #
39
- #
40
- # == Authors
41
- #
42
- # * Michael Granger <ged@FaerieMUD.org>
43
- # * Mahlon E. Smith <mahlon@martini.nu>
44
- #
45
- # :include: LICENSE
46
- #
47
- #--
48
- #
49
- # Please see the file LICENSE in the base directory for licensing details.
50
- #
51
39
  class Treequel::Filter
52
40
  include Treequel::Loggable,
53
41
  Treequel::Constants::Patterns
@@ -78,6 +66,7 @@ class Treequel::Filter
78
66
 
79
67
 
80
68
  ### An abstract class for filter components.
69
+ ### @abstract Subclass and override {#to_s} to implement a custom Component class.
81
70
  class Component
82
71
  include Treequel::Loggable
83
72
 
@@ -6,17 +6,64 @@ require 'etc'
6
6
  require 'logger'
7
7
 
8
8
  require 'treequel'
9
+ require 'treequel/constants'
9
10
 
10
11
 
11
- #--
12
- # A collection of mixins shared between Treequel classes. Stolen mostly from ThingFish.
13
- #
14
- module Treequel # :nodoc:
12
+ module Treequel
15
13
 
16
14
  # A collection of various delegation code-generators that can be used to define
17
15
  # delegation through other methods, to instance variables, etc.
18
16
  module Delegation
19
17
 
18
+ ###############
19
+ module_function
20
+ ###############
21
+
22
+ ### Define the given +delegated_methods+ as delegators to the like-named method
23
+ ### of the return value of the +delegate_method+.
24
+ ###
25
+ ### @example
26
+ ### class MyClass
27
+ ### extend Treequel::Delegation
28
+ ###
29
+ ### # Delegate the #bound?, #err, and #result2error methods to the connection
30
+ ### # object returned by the #connection method. This allows the connection
31
+ ### # to still be loaded on demand/overridden/etc.
32
+ ### def_method_delegators :connection, :bound?, :err, :result2error
33
+ ###
34
+ ### def connection
35
+ ### @connection ||= self.connect
36
+ ### end
37
+ ### end
38
+ ###
39
+ def def_method_delegators( delegate_method, *delegated_methods )
40
+ delegated_methods.each do |name|
41
+ body = make_method_delegator( delegate_method, name )
42
+ define_method( name, &body )
43
+ end
44
+ end
45
+
46
+
47
+ ### Define the given +delegated_methods+ as delegators to the like-named method
48
+ ### of the specified +ivar+. This is pretty much identical with how 'Forwardable'
49
+ ### from the stdlib does delegation, but it's reimplemented here for consistency.
50
+ ###
51
+ ### class MyClass
52
+ ### extend Treequel::Delegation
53
+ ###
54
+ ### # Delegate the #each method to the @collection ivar
55
+ ### def_ivar_delegators :@collection, :each
56
+ ###
57
+ ### end
58
+ ###
59
+ def def_ivar_delegators( ivar, *delegated_methods )
60
+ delegated_methods.each do |name|
61
+ body = make_ivar_delegator( ivar, name )
62
+ define_method( name, &body )
63
+ end
64
+ end
65
+
66
+
20
67
  #######
21
68
  private
22
69
  #######
@@ -64,59 +111,46 @@ module Treequel # :nodoc:
64
111
  return eval( code, nil, file, line.to_i )
65
112
  end
66
113
 
114
+ end # module Delegation
115
+
116
+
117
+ # A collection of key-normalization functions for various artifacts in LDAP like
118
+ # attribute names, objectclass OIDs, etc.
119
+ module Normalization
67
120
 
68
121
  ###############
69
122
  module_function
70
123
  ###############
71
124
 
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
125
+ ### Normalize the given key
126
+ ### @param [String] key the key to normalize
127
+ ### @return a downcased Symbol stripped of any invalid characters, and
128
+ ### with '-' characters converted to '_'.
129
+ def normalize_key( key )
130
+ return key if key.to_s =~ Treequel::Constants::Patterns::NUMERICOID
131
+ return key.to_s.downcase.
132
+ gsub( /[^[:alnum:]\-_]/, '' ).
133
+ gsub( '-', '_' ).
134
+ to_sym
93
135
  end
94
136
 
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 )
137
+ ### Return a copy of +hash+ with all of its keys normalized by #normalize_key.
138
+ ### @param [Hash] hash the Hash to normalize
139
+ def normalize_hash( hash )
140
+ hash = hash.dup
141
+ hash.keys.each do |key|
142
+ nkey = normalize_key( key )
143
+ hash[ nkey ] = hash.delete( key ) if key != nkey
112
144
  end
145
+
146
+ return hash
113
147
  end
114
148
 
115
149
 
116
- end # module Delegation
150
+ end # Normalization
117
151
 
118
152
 
119
- # Add logging to a Treequel class. Including classes get #log and #log_debug methods.
153
+ ### Add logging to a Treequel class. Including classes get #log and #log_debug methods.
120
154
  module Loggable
121
155
 
122
156
  LEVEL = {
@@ -129,7 +163,8 @@ module Treequel # :nodoc:
129
163
 
130
164
  ### A logging proxy class that wraps calls to the logger into calls that include
131
165
  ### the name of the calling class.
132
- class ClassNameProxy # :nodoc:
166
+ ### @private
167
+ class ClassNameProxy
133
168
 
134
169
  ### Create a new proxy for the given +klass+.
135
170
  def initialize( klass, force_debug=false )
@@ -292,7 +327,8 @@ module Treequel # :nodoc:
292
327
  def predicate_attr( *symbols )
293
328
  symbols.each do |attrname|
294
329
  define_method( "#{attrname}?" ) do
295
- instance_variable_get( "@#{attrname}" ) ? true : false
330
+ instance_variable_defined?( "@#{attrname}" ) &&
331
+ instance_variable_get( "@#{attrname}" ) ? true : false
296
332
  end
297
333
  define_method( "#{attrname}=" ) do |newval|
298
334
  instance_variable_set( "@#{attrname}", newval ? true : false )
@@ -341,7 +377,7 @@ module Treequel # :nodoc:
341
377
  return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
342
378
  attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
343
379
 
344
- if attributes.empty?
380
+ if attributes.empty?
345
381
  return ''
346
382
  else
347
383
  return "\e[%sm" % attributes
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ldap'
4
+ require 'ldap/control'
5
+
6
+ require 'treequel'
7
+
8
+ ### Extensions to LDAP::Control to make them grok ==.
9
+ module Treequel::LDAPControlExtensions
10
+
11
+ ### Returns +true+ if the +other+ LDAP::Control is equivalent to the receiver.
12
+ def ==( other )
13
+ return ( other.class == self.class ) &&
14
+ other.oid == self.oid &&
15
+ other.value == self.value &&
16
+ other.iscritical == self.iscritical
17
+ end
18
+
19
+ end # module Treequel::LDAPControlExtensions
20
+
21
+
22
+ # Include Treequel-specific extensions as a mixin.
23
+ # @private
24
+ class LDAP::Control
25
+ include Treequel::LDAPControlExtensions
26
+ end
27
+
28
+
29
+
@@ -25,6 +25,7 @@ class Treequel::Schema
25
25
  include Treequel::Loggable,
26
26
  Treequel::Constants::Patterns
27
27
 
28
+ require 'treequel/schema/table'
28
29
  require 'treequel/schema/objectclass'
29
30
  require 'treequel/schema/attributetype'
30
31
  require 'treequel/schema/matchingrule'
@@ -168,14 +169,14 @@ class Treequel::Schema
168
169
  ### return them as a Hash keyed both by numeric OID and by each of its NAME attributes (if it
169
170
  ### has any).
170
171
  def parse_objectclasses( descriptions )
171
- return descriptions.inject( {} ) do |hash, desc|
172
+ return descriptions.inject( Treequel::Schema::Table.new ) do |table, desc|
172
173
  oc = Treequel::Schema::ObjectClass.parse( self, desc ) or
173
174
  raise Treequel::Error, "couldn't create an objectClass from %p" % [ desc ]
174
175
 
175
- hash[ oc.oid ] = oc
176
- oc.names.inject( hash ) {|h, name| h[name] = oc; h }
176
+ table[ oc.oid ] = oc
177
+ oc.names.inject( table ) {|h, name| h[name] = oc; h }
177
178
 
178
- hash
179
+ table
179
180
  end
180
181
  end
181
182
 
@@ -184,14 +185,14 @@ class Treequel::Schema
184
185
  ### and return them as a Hash keyed both by numeric OID and by each of its NAME attributes
185
186
  ### (if it has any).
186
187
  def parse_attribute_types( descriptions )
187
- return descriptions.inject( {} ) do |hash, desc|
188
+ return descriptions.inject( Treequel::Schema::Table.new ) do |table, desc|
188
189
  attrtype = Treequel::Schema::AttributeType.parse( self, desc ) or
189
190
  raise Treequel::Error, "couldn't create an attributeType from %p" % [ desc ]
190
191
 
191
- hash[ attrtype.oid ] = attrtype
192
- attrtype.names.inject( hash ) {|h, name| h[name] = attrtype; h }
192
+ table[ attrtype.oid ] = attrtype
193
+ attrtype.names.inject( table ) {|h, name| h[name] = attrtype; h }
193
194
 
194
- hash
195
+ table
195
196
  end
196
197
  end
197
198
 
@@ -199,12 +200,13 @@ class Treequel::Schema
199
200
  ### Parse the given LDAP syntax +descriptions+ into Treequel::Schema::LDAPSyntax objects and
200
201
  ### return them as a Hash keyed by numeric OID.
201
202
  def parse_ldap_syntaxes( descriptions )
202
- return descriptions.inject( {} ) do |hash, desc|
203
+ descriptions ||= []
204
+ return descriptions.inject( Treequel::Schema::Table.new ) do |table, desc|
203
205
  syntax = Treequel::Schema::LDAPSyntax.parse( self, desc ) or
204
206
  raise Treequel::Error, "couldn't create an LDAPSyntax from %p" % [ desc ]
205
207
 
206
- hash[ syntax.oid ] = syntax
207
- hash
208
+ table[ syntax.oid ] = syntax
209
+ table
208
210
  end
209
211
  end
210
212
 
@@ -213,14 +215,15 @@ class Treequel::Schema
213
215
  ### and return them as a Hash keyed both by numeric OID and by each of its NAME attributes
214
216
  ### (if it has any).
215
217
  def parse_matching_rules( descriptions )
216
- return descriptions.inject( {} ) do |hash, desc|
218
+ descriptions ||= []
219
+ return descriptions.inject( Treequel::Schema::Table.new ) do |table, desc|
217
220
  rule = Treequel::Schema::MatchingRule.parse( self, desc ) or
218
221
  raise Treequel::Error, "couldn't create an matchingRule from %p" % [ desc ]
219
222
 
220
- hash[ rule.oid ] = rule
221
- rule.names.inject( hash ) {|h, name| h[name] = rule; h }
223
+ table[ rule.oid ] = rule
224
+ rule.names.inject( table ) {|h, name| h[name] = rule; h }
222
225
 
223
- hash
226
+ table
224
227
  end
225
228
  end
226
229
 
@@ -229,14 +232,15 @@ class Treequel::Schema
229
232
  ### and return them as a Hash keyed both by numeric OID and by each of its NAME attributes
230
233
  ### (if it has any).
231
234
  def parse_matching_rule_uses( descriptions )
232
- return descriptions.inject( {} ) do |hash, desc|
235
+ descriptions ||= []
236
+ return descriptions.inject( Treequel::Schema::Table.new ) do |table, desc|
233
237
  ruleuse = Treequel::Schema::MatchingRuleUse.parse( self, desc ) or
234
238
  raise Treequel::Error, "couldn't create an matchingRuleUse from %p" % [ desc ]
235
239
 
236
- hash[ ruleuse.oid ] = ruleuse
237
- ruleuse.names.inject( hash ) {|h, name| h[name] = ruleuse; h }
240
+ table[ ruleuse.oid ] = ruleuse
241
+ ruleuse.names.inject( table ) {|h, name| h[name] = ruleuse; h }
238
242
 
239
- hash
243
+ table
240
244
  end
241
245
  end
242
246
 
@@ -23,6 +23,7 @@ require 'treequel/exceptions'
23
23
  #
24
24
  class Treequel::Schema::AttributeType
25
25
  include Treequel::Loggable,
26
+ Treequel::Normalization,
26
27
  Treequel::Constants::Patterns
27
28
 
28
29
  extend Treequel::AttributeDeclarations
@@ -157,6 +158,20 @@ class Treequel::Schema::AttributeType
157
158
  end
158
159
 
159
160
 
161
+ ### Return the attributeType's names after normalizing them.
162
+ def normalized_names
163
+ return self.names.collect {|name| normalize_key(name) }
164
+ end
165
+
166
+
167
+ ### Returns +true+ if the specified +name+ is one of the attribute's names
168
+ ### after normalization of both.
169
+ def valid_name?( name )
170
+ normname = normalize_key( name )
171
+ return self.normalized_names.include?( normname )
172
+ end
173
+
174
+
160
175
  ### Return the Treequel::Schema::AttributeType instance that corresponds to
161
176
  ### the receiver's superior type. If the attributeType doesn't have a SUP
162
177
  ### attribute, this method returns +nil+.
@@ -236,14 +251,29 @@ class Treequel::Schema::AttributeType
236
251
  protected
237
252
  #########
238
253
 
254
+ OID_SPLIT_PATTERN = /
255
+ ^
256
+ #{SQUOTE}?
257
+ (#{OID}) # OID = $1
258
+ #{SQUOTE}?
259
+ (?:
260
+ #{LCURLY}
261
+ (#{LEN}) # Length = $2
262
+ #{RCURLY}
263
+ )?$
264
+ /x
265
+
239
266
  ### Split a numeric OID with an optional length qualifier into a numeric OID and length. If
240
267
  ### no length qualifier is present, it will be nil.
268
+ ### NOTE: Modified to support ActiveDirectory schemas, which have both quoted numeric OIDs
269
+ ### and descriptors as syntax OIDs.
241
270
  def split_syntax_oid( noidlen )
242
- unless noidlen =~ /^(#{NUMERICOID}) (?:#{LCURLY} (#{LEN}) #{RCURLY})?$/x
243
- raise Treequel::ParseError, "invalid numeric syntax OID with length: %p" % [ noidlen ]
271
+ unless noidlen =~ OID_SPLIT_PATTERN
272
+ raise Treequel::ParseError, "invalid syntax OID: %p" % [ noidlen ]
244
273
  end
245
274
 
246
- oid, len = $1, $2
275
+ oidstring, len = $1, $2
276
+ oid = Treequel::Schema.parse_oid( oidstring )
247
277
 
248
278
  return oid, len ? Integer(len) : nil
249
279
  end
@@ -9,17 +9,6 @@ require 'treequel/exceptions'
9
9
 
10
10
 
11
11
  # This is a class for representing ldapSyntax declarations in a Treequel::Schema.
12
- #
13
- # == Authors
14
- #
15
- # * Michael Granger <ged@FaerieMUD.org>
16
- #
17
- # :include: LICENSE
18
- #
19
- #--
20
- #
21
- # Please see the file LICENSE in the base directory for licensing details.
22
- #
23
12
  class Treequel::Schema::LDAPSyntax
24
13
  include Treequel::Loggable,
25
14
  Treequel::Constants::Patterns