treequel 1.0.1 → 1.0.4

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 (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