treequel 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +354 -0
- data/LICENSE +27 -0
- data/README +66 -0
- data/Rakefile +345 -0
- data/Rakefile.local +43 -0
- data/bin/treeirb +14 -0
- data/bin/treequel +229 -0
- data/examples/company-directory.rb +112 -0
- data/examples/ldap-monitor.rb +143 -0
- data/examples/ldap-monitor/public/css/master.css +328 -0
- data/examples/ldap-monitor/public/images/card_small.png +0 -0
- data/examples/ldap-monitor/public/images/chain_small.png +0 -0
- data/examples/ldap-monitor/public/images/globe_small.png +0 -0
- data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
- data/examples/ldap-monitor/public/images/plug.png +0 -0
- data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
- data/examples/ldap-monitor/public/images/tick.png +0 -0
- data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
- data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
- data/examples/ldap-monitor/views/backends.erb +41 -0
- data/examples/ldap-monitor/views/connections.erb +74 -0
- data/examples/ldap-monitor/views/databases.erb +39 -0
- data/examples/ldap-monitor/views/dump_subsystem.erb +14 -0
- data/examples/ldap-monitor/views/index.erb +14 -0
- data/examples/ldap-monitor/views/layout.erb +35 -0
- data/examples/ldap-monitor/views/listeners.erb +30 -0
- data/examples/ldap_state.rb +62 -0
- data/lib/treequel.rb +145 -0
- data/lib/treequel/branch.rb +589 -0
- data/lib/treequel/branchcollection.rb +204 -0
- data/lib/treequel/branchset.rb +360 -0
- data/lib/treequel/constants.rb +604 -0
- data/lib/treequel/directory.rb +541 -0
- data/lib/treequel/exceptions.rb +32 -0
- data/lib/treequel/filter.rb +704 -0
- data/lib/treequel/mixins.rb +325 -0
- data/lib/treequel/schema.rb +245 -0
- data/lib/treequel/schema/attributetype.rb +252 -0
- data/lib/treequel/schema/ldapsyntax.rb +96 -0
- data/lib/treequel/schema/matchingrule.rb +124 -0
- data/lib/treequel/schema/matchingruleuse.rb +124 -0
- data/lib/treequel/schema/objectclass.rb +289 -0
- data/lib/treequel/sequel_integration.rb +26 -0
- data/lib/treequel/utils.rb +169 -0
- data/rake/191_compat.rb +26 -0
- data/rake/dependencies.rb +76 -0
- data/rake/helpers.rb +434 -0
- data/rake/hg.rb +261 -0
- data/rake/manual.rb +782 -0
- data/rake/packaging.rb +135 -0
- data/rake/publishing.rb +318 -0
- data/rake/rdoc.rb +30 -0
- data/rake/style.rb +62 -0
- data/rake/svn.rb +668 -0
- data/rake/testing.rb +187 -0
- data/rake/verifytask.rb +64 -0
- data/rake/win32.rb +190 -0
- data/spec/lib/constants.rb +93 -0
- data/spec/lib/helpers.rb +100 -0
- data/spec/treequel/branch_spec.rb +569 -0
- data/spec/treequel/branchcollection_spec.rb +213 -0
- data/spec/treequel/branchset_spec.rb +376 -0
- data/spec/treequel/directory_spec.rb +487 -0
- data/spec/treequel/filter_spec.rb +482 -0
- data/spec/treequel/mixins_spec.rb +330 -0
- data/spec/treequel/schema/attributetype_spec.rb +237 -0
- data/spec/treequel/schema/ldapsyntax_spec.rb +83 -0
- data/spec/treequel/schema/matchingrule_spec.rb +158 -0
- data/spec/treequel/schema/matchingruleuse_spec.rb +137 -0
- data/spec/treequel/schema/objectclass_spec.rb +262 -0
- data/spec/treequel/schema_spec.rb +118 -0
- data/spec/treequel/utils_spec.rb +49 -0
- data/spec/treequel_spec.rb +179 -0
- metadata +169 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'treequel'
|
4
|
+
|
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
|
+
module Treequel
|
19
|
+
|
20
|
+
### The base Treequel exception type
|
21
|
+
class Error < ::RuntimeError; end
|
22
|
+
|
23
|
+
### Schema parsing errors
|
24
|
+
class ParseError < Treequel::Error; end
|
25
|
+
|
26
|
+
### Exception type raised when an expression cannot be parsed from the
|
27
|
+
### arguments given to Treequel::Filter.new
|
28
|
+
class ExpressionError < Treequel::Error; end
|
29
|
+
|
30
|
+
end # module Treequel
|
31
|
+
|
32
|
+
|
@@ -0,0 +1,704 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ldap'
|
4
|
+
|
5
|
+
require 'treequel'
|
6
|
+
require 'treequel/branchset'
|
7
|
+
require 'treequel/exceptions'
|
8
|
+
require 'treequel/sequel_integration'
|
9
|
+
|
10
|
+
|
11
|
+
# This is an object that is used to build an LDAP filter for Treequel::Branchsets.
|
12
|
+
#
|
13
|
+
# == Grammar (from RFC 2254) ==
|
14
|
+
#
|
15
|
+
# filter = "(" filtercomp ")"
|
16
|
+
# filtercomp = and / or / not / item
|
17
|
+
# and = "&" filterlist
|
18
|
+
# or = "|" filterlist
|
19
|
+
# not = "!" filter
|
20
|
+
# filterlist = 1*filter
|
21
|
+
# item = simple / present / substring / extensible
|
22
|
+
# simple = attr filtertype value
|
23
|
+
# filtertype = equal / approx / greater / less
|
24
|
+
# equal = "="
|
25
|
+
# approx = "~="
|
26
|
+
# greater = ">="
|
27
|
+
# less = "<="
|
28
|
+
# extensible = attr [":dn"] [":" matchingrule] ":=" value
|
29
|
+
# / [":dn"] ":" matchingrule ":=" value
|
30
|
+
# present = attr "=*"
|
31
|
+
# substring = attr "=" [initial] any [final]
|
32
|
+
# initial = value
|
33
|
+
# any = "*" *(value "*")
|
34
|
+
# final = value
|
35
|
+
# attr = AttributeDescription from Section 4.1.5 of [1]
|
36
|
+
# matchingrule = MatchingRuleId from Section 4.1.9 of [1]
|
37
|
+
# value = AttributeValue from Section 4.1.6 of [1]
|
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
|
+
class Treequel::Filter
|
52
|
+
include Treequel::Loggable,
|
53
|
+
Treequel::Constants::Patterns
|
54
|
+
|
55
|
+
### Filter list component of a Treequel::Filter.
|
56
|
+
class FilterList
|
57
|
+
include Treequel::Loggable
|
58
|
+
|
59
|
+
### Create a new filter list with the given +filters+ in it.
|
60
|
+
def initialize( *filters )
|
61
|
+
@filters = filters.flatten
|
62
|
+
end
|
63
|
+
|
64
|
+
######
|
65
|
+
public
|
66
|
+
######
|
67
|
+
|
68
|
+
# The filters in the FilterList
|
69
|
+
attr_reader :filters
|
70
|
+
|
71
|
+
|
72
|
+
### Return the FilterList as a string.
|
73
|
+
def to_s
|
74
|
+
return self.filters.collect {|f| f.to_s }.join
|
75
|
+
end
|
76
|
+
|
77
|
+
end # class FilterList
|
78
|
+
|
79
|
+
|
80
|
+
### An abstract class for filter components.
|
81
|
+
class Component
|
82
|
+
include Treequel::Loggable
|
83
|
+
|
84
|
+
# Hide this class's new method
|
85
|
+
private_class_method :new
|
86
|
+
|
87
|
+
### Inherited hook: re-expose inheriting class's .new method
|
88
|
+
def self::inherited( klass )
|
89
|
+
klass.module_eval( 'public_class_method :new' )
|
90
|
+
super
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
### Stringify the component.
|
95
|
+
### :TODO: If this doesn't end up being a refactored version of all of its
|
96
|
+
### subclasses's #to_s methods, test that it needs overriding.
|
97
|
+
def to_s
|
98
|
+
raise NotImplementedError, "%s does not provide an implementation of #to_s" %
|
99
|
+
[ self.class.name ]
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
### Return a human-readable string representation of the component suitable
|
104
|
+
### for debugging.
|
105
|
+
def inspect
|
106
|
+
return %Q{#<%s:0x%0x "%s">} % [
|
107
|
+
self.class.name,
|
108
|
+
self.object_id * 2,
|
109
|
+
self.to_s,
|
110
|
+
]
|
111
|
+
end
|
112
|
+
|
113
|
+
### Components are non-promiscuous (don't match everything) by default.
|
114
|
+
def promiscuous?
|
115
|
+
return false
|
116
|
+
end
|
117
|
+
|
118
|
+
end # class Component
|
119
|
+
|
120
|
+
|
121
|
+
### A filtercomp that negates the filter it contains.
|
122
|
+
class NotComponent < Treequel::Filter::Component
|
123
|
+
|
124
|
+
### Create an negation component of the specified +filter+.
|
125
|
+
def initialize( filter )
|
126
|
+
@filter = filter
|
127
|
+
end
|
128
|
+
|
129
|
+
### Return the stringified filter form of the receiver.
|
130
|
+
def to_s
|
131
|
+
return '!' + @filter.to_s
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
### An 'and' filter component
|
138
|
+
class AndComponent < Treequel::Filter::Component
|
139
|
+
|
140
|
+
### Create a new 'and' filter component with the given +filterlist+.
|
141
|
+
def initialize( *filterlist )
|
142
|
+
@filterlist = Treequel::Filter::FilterList.new( filterlist )
|
143
|
+
super()
|
144
|
+
end
|
145
|
+
|
146
|
+
# The list of filters to AND together in the filter string
|
147
|
+
attr_reader :filterlist
|
148
|
+
|
149
|
+
### Stringify the item
|
150
|
+
def to_s
|
151
|
+
return '&' + @filterlist.to_s
|
152
|
+
end
|
153
|
+
|
154
|
+
end # AndComponent
|
155
|
+
|
156
|
+
|
157
|
+
### An 'or' filter component
|
158
|
+
class OrComponent < Treequel::Filter::Component
|
159
|
+
|
160
|
+
### Create a new 'or' filter component with the given +filterlist+.
|
161
|
+
def initialize( *filterlist )
|
162
|
+
@filterlist = Treequel::Filter::FilterList.new( filterlist )
|
163
|
+
super()
|
164
|
+
end
|
165
|
+
|
166
|
+
# The list of filters to OR together in the filter string
|
167
|
+
attr_reader :filterlist
|
168
|
+
|
169
|
+
### Stringify the item
|
170
|
+
def to_s
|
171
|
+
return '|' + @filterlist.to_s
|
172
|
+
end
|
173
|
+
|
174
|
+
end # class OrComponent
|
175
|
+
|
176
|
+
|
177
|
+
### An 'item' filter component
|
178
|
+
class ItemComponent < Treequel::Filter::Component; end
|
179
|
+
|
180
|
+
|
181
|
+
### A simple (attribute=value) component
|
182
|
+
### simple = attr filtertype value
|
183
|
+
### filtertype = equal / approx / greater / less
|
184
|
+
### equal = "="
|
185
|
+
### approx = "~="
|
186
|
+
### greater = ">="
|
187
|
+
### less = "<="
|
188
|
+
class SimpleItemComponent < Treequel::Filter::ItemComponent
|
189
|
+
|
190
|
+
# The valid values for +filtertype+ and the equivalent operator
|
191
|
+
FILTERTYPE_OP = {
|
192
|
+
:equal => '=',
|
193
|
+
:approx => '~=',
|
194
|
+
:greater => '>=',
|
195
|
+
:less => '<=',
|
196
|
+
}
|
197
|
+
FILTERTYPE_OP.freeze
|
198
|
+
|
199
|
+
# Inverse of the FILTERTYPE_OP mapping (symbol -> name)
|
200
|
+
FILTEROP_NAMES = FILTERTYPE_OP.invert.freeze
|
201
|
+
|
202
|
+
# A regex that matches any of the 'simple' operators.
|
203
|
+
FILTER_SPLIT_REGEXP = Regexp.new( '(' + FILTERTYPE_OP.values.join('|') + ')' )
|
204
|
+
FILTER_SPLIT_REGEXP.freeze
|
205
|
+
|
206
|
+
|
207
|
+
### Parse a new SimpleItemComponent from the specified +literal+.
|
208
|
+
def self::parse_from_string( literal )
|
209
|
+
parts = literal.split( FILTER_SPLIT_REGEXP, 3 )
|
210
|
+
unless parts.length == 3
|
211
|
+
raise Treequel::ExpressionError,
|
212
|
+
"unable to parse %p as a string literal" % [ literal ]
|
213
|
+
end
|
214
|
+
|
215
|
+
attribute, operator, value = *parts
|
216
|
+
filtertype = FILTEROP_NAMES[ operator ]
|
217
|
+
|
218
|
+
return self.new( attribute, value, filtertype )
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
### Create a new 'simple' item filter component with the given
|
223
|
+
### +attribute+, +filtertype+, and +value+. The +filtertype+ should
|
224
|
+
### be one of: :equal, :approx, :greater, :less
|
225
|
+
def initialize( attribute, value, filtertype=:equal )
|
226
|
+
self.log.debug "creating a new %s %s for %p and %p" %
|
227
|
+
[ filtertype, self.class.name, attribute, value ]
|
228
|
+
|
229
|
+
filtertype = filtertype.to_s.downcase.to_sym
|
230
|
+
|
231
|
+
if FILTERTYPE_OP.key?( filtertype )
|
232
|
+
# no-op
|
233
|
+
elsif FILTEROP_NAMES.key?( filtertype.to_s )
|
234
|
+
filtertype = FILTEROP_NAMES[ filtertype.to_s ]
|
235
|
+
else
|
236
|
+
raise Treequel::ExpressionError,
|
237
|
+
"invalid simple item operator %p" % [ filtertype ]
|
238
|
+
end
|
239
|
+
|
240
|
+
@attribute = attribute
|
241
|
+
@value = value
|
242
|
+
@filtertype = filtertype
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
######
|
247
|
+
public
|
248
|
+
######
|
249
|
+
|
250
|
+
# The name of the item's attribute
|
251
|
+
attr_accessor :attribute
|
252
|
+
|
253
|
+
# The item's value
|
254
|
+
attr_accessor :value
|
255
|
+
|
256
|
+
# The item's filter type (one of FILTERTYPE_OP.keys)
|
257
|
+
attr_accessor :filtertype
|
258
|
+
|
259
|
+
|
260
|
+
### The operator that is associated with the item's +filtertype+.
|
261
|
+
def filtertype_op
|
262
|
+
FILTERTYPE_OP[ self.filtertype.to_sym ]
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
### Stringify the component
|
267
|
+
def to_s
|
268
|
+
return [ self.attribute, self.filtertype_op, self.value ].join
|
269
|
+
end
|
270
|
+
|
271
|
+
end # class SimpleItemComponent
|
272
|
+
|
273
|
+
|
274
|
+
### A 'present' (attribute=*) component
|
275
|
+
class PresentItemComponent < Treequel::Filter::ItemComponent
|
276
|
+
|
277
|
+
# The default attribute to test for presence if none is specified
|
278
|
+
DEFAULT_ATTRIBUTE = :objectClass
|
279
|
+
|
280
|
+
### Create a new 'presence' item filter component for the given +attribute+.
|
281
|
+
def initialize( attribute=DEFAULT_ATTRIBUTE )
|
282
|
+
@attribute = attribute
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
### Stringify the component
|
287
|
+
def to_s
|
288
|
+
return @attribute.to_s + '=*'
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
### Returns true, indicating that this component in a filter will match every
|
293
|
+
### entry if its attribute is 'objectClass'.
|
294
|
+
def promiscuous?
|
295
|
+
return @attribute.to_sym == DEFAULT_ATTRIBUTE
|
296
|
+
end
|
297
|
+
|
298
|
+
end # class PresentItemComponent
|
299
|
+
|
300
|
+
|
301
|
+
### A 'substring' (attribute=foo*) component
|
302
|
+
class SubstringItemComponent < Treequel::Filter::ItemComponent
|
303
|
+
include Treequel::Constants::Patterns
|
304
|
+
|
305
|
+
|
306
|
+
### Parse the substring item from the given +literal+.
|
307
|
+
def self::parse_from_string( literal )
|
308
|
+
match = LDAP_SUBSTRING_FILTER.match( literal ) or
|
309
|
+
raise Treequel::ExpressionError,
|
310
|
+
"unable to parse %p as a substring literal" % [ literal ]
|
311
|
+
|
312
|
+
Treequel.logger.debug " parsed substring literal as: %p" % [ match.captures ]
|
313
|
+
return self.new( *(match.captures.values_at(1,3,2)) )
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
#############################################################
|
318
|
+
### I N S T A N C E M E T H O D S
|
319
|
+
#############################################################
|
320
|
+
|
321
|
+
### Create a new 'presence' item filter component for the given +attribute+.
|
322
|
+
def initialize( attribute, pattern, options=nil )
|
323
|
+
@attribute = attribute
|
324
|
+
@pattern = pattern
|
325
|
+
@options = options
|
326
|
+
|
327
|
+
super()
|
328
|
+
end
|
329
|
+
|
330
|
+
|
331
|
+
######
|
332
|
+
public
|
333
|
+
######
|
334
|
+
|
335
|
+
# The name of the attribute to match against
|
336
|
+
attr_accessor :attribute
|
337
|
+
|
338
|
+
# The pattern to match (if the index exists in the directory)
|
339
|
+
attr_accessor :pattern
|
340
|
+
|
341
|
+
# The attribute options
|
342
|
+
attr_accessor :options
|
343
|
+
|
344
|
+
|
345
|
+
### Stringify the component
|
346
|
+
def to_s
|
347
|
+
return self.attribute.to_s + self.options.to_s + '=' + self.pattern
|
348
|
+
end
|
349
|
+
|
350
|
+
end # class SubstringItemComponent
|
351
|
+
|
352
|
+
|
353
|
+
|
354
|
+
#################################################################
|
355
|
+
### F I L T E R C O N S T A N T S
|
356
|
+
#################################################################
|
357
|
+
|
358
|
+
# The default filter expression to use when searching if none is specified
|
359
|
+
DEFAULT_EXPRESSION = [ :objectClass ]
|
360
|
+
DEFAULT_EXPRESSION.freeze
|
361
|
+
|
362
|
+
# The mapping of leftmost symbols in a boolean expression and the
|
363
|
+
# corresponding FilterComponent class.
|
364
|
+
LOGICAL_COMPONENTS = {
|
365
|
+
:or => OrComponent,
|
366
|
+
:| => OrComponent,
|
367
|
+
:and => AndComponent,
|
368
|
+
:& => AndComponent,
|
369
|
+
:not => NotComponent,
|
370
|
+
:"!" => NotComponent,
|
371
|
+
}
|
372
|
+
|
373
|
+
# An equivalence mapping of operation names from Sequel expressions into
|
374
|
+
# Treequel equivalents
|
375
|
+
SEQUEL_FILTERTYPE_EQUIVALENTS = {
|
376
|
+
:like => :equal,
|
377
|
+
:>= => :greater,
|
378
|
+
:<= => :less,
|
379
|
+
}
|
380
|
+
SEQUEL_FILTERTYPE_EQUIVALENTS.freeze
|
381
|
+
|
382
|
+
# A list of filtertypes that come in as Sequel::Expressions; these generated nicer
|
383
|
+
# exception messages that just 'unknown filtertype'
|
384
|
+
UNSUPPORTED_SEQUEL_FILTERTYPES = {
|
385
|
+
:'~*' => %{LDAP doesn't support Regex filters},
|
386
|
+
:'~' => %{LDAP doesn't support Regex filters},
|
387
|
+
:> => %{LDAP doesn't support "greater-than"; use "greater-than-or-equal-to" (>=) instead},
|
388
|
+
:< => %{LDAP doesn't support "less-than"; use "less-than-or-equal-to" (<=) instead},
|
389
|
+
}
|
390
|
+
|
391
|
+
|
392
|
+
|
393
|
+
### Turn the specified filter +expression+ into a Treequel::Filter::Component
|
394
|
+
### object and return it.
|
395
|
+
def self::parse_expression( expression )
|
396
|
+
Treequel.logger.debug "Parsing expression %p" % [ expression ]
|
397
|
+
expression = expression[0] if expression.is_a?( Array ) && expression.length == 1
|
398
|
+
|
399
|
+
case expression
|
400
|
+
|
401
|
+
# String-literal filters
|
402
|
+
when String
|
403
|
+
return expression
|
404
|
+
|
405
|
+
# 'Item' components
|
406
|
+
when Array
|
407
|
+
return self.parse_array_expression( expression )
|
408
|
+
|
409
|
+
# Composite item components
|
410
|
+
when Hash
|
411
|
+
return self.parse_hash_expression( expression )
|
412
|
+
|
413
|
+
# Unwrapped presence item filter
|
414
|
+
when Symbol
|
415
|
+
return Treequel::Filter::PresentItemComponent.new( expression )
|
416
|
+
|
417
|
+
# Support Sequel expressions
|
418
|
+
when Sequel::SQL::Expression
|
419
|
+
return self.parse_sequel_expression( expression )
|
420
|
+
|
421
|
+
# Filters and components can already act as components of other filters
|
422
|
+
when Treequel::Filter, Treequel::Filter::Component
|
423
|
+
return expression
|
424
|
+
|
425
|
+
else
|
426
|
+
raise Treequel::ExpressionError,
|
427
|
+
"don't know how to turn %p into an filter component" % [ expression ]
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
|
432
|
+
### Turn the specified expression Array into a Treequel::Filter::Component object
|
433
|
+
### and return it.
|
434
|
+
def self::parse_array_expression( expression )
|
435
|
+
Treequel.logger.debug "Parsing Array expression %p" % [ expression ]
|
436
|
+
|
437
|
+
case
|
438
|
+
|
439
|
+
# [ ] := '(objectClass=*)'
|
440
|
+
when expression.empty?
|
441
|
+
Treequel.logger.debug " empty expression -> objectClass presence item component"
|
442
|
+
return Treequel::Filter::PresentItemComponent.new
|
443
|
+
|
444
|
+
# Collection of subfilters
|
445
|
+
# [ [:uid, 'mahlon'], [:employeeNumber, 20202] ]
|
446
|
+
when expression.all? {|elem| elem.is_a?(Array) }
|
447
|
+
Treequel.logger.debug " parsing array of subfilters"
|
448
|
+
filters = expression.collect {|exp| Treequel::Filter.new(exp) }
|
449
|
+
if filters.length > 1
|
450
|
+
return Treequel::Filter::AndComponent.new( filters )
|
451
|
+
else
|
452
|
+
return filters.first
|
453
|
+
end
|
454
|
+
|
455
|
+
# Literal filters [ 'uid~=gung', 'l=bangkok' ] := '(uid~=gung)(l=bangkok)'
|
456
|
+
when expression.all? {|item| item.is_a?(String) }
|
457
|
+
filters = expression.collect {|item| Treequel::Filter.new(item) }
|
458
|
+
return Treequel::Filter::FilterList.new( filters )
|
459
|
+
|
460
|
+
# Collection of subfilter objects
|
461
|
+
when expression.all? {|elem| elem.is_a?(Treequel::Filter) }
|
462
|
+
return Treequel::Filter::FilterList.new( expression )
|
463
|
+
|
464
|
+
# [ :attribute ] := '(attribute=*)'
|
465
|
+
when expression.length == 1
|
466
|
+
return self.parse_expression( expression[0] )
|
467
|
+
|
468
|
+
when expression[0].is_a?( Symbol )
|
469
|
+
return self.parse_tuple_array_expression( expression )
|
470
|
+
|
471
|
+
else
|
472
|
+
raise Treequel::ExpressionError,
|
473
|
+
"don't know how to turn %p into a filter component" % [ expression ]
|
474
|
+
end
|
475
|
+
|
476
|
+
end
|
477
|
+
|
478
|
+
|
479
|
+
### Parse one or more tuples contained in a Hash into an ANDed set of
|
480
|
+
### Treequel::Filter::Components and return it.
|
481
|
+
def self::parse_hash_expression( expression )
|
482
|
+
Treequel.logger.debug "Parsing Hash expression %p" % [ expression ]
|
483
|
+
|
484
|
+
filterlist = expression.collect do |key, expr|
|
485
|
+
Treequel.logger.debug " adding %p => %p to the filter list" % [ key, expr ]
|
486
|
+
if expr.respond_to?( :fetch )
|
487
|
+
Treequel.logger.debug " ORing together %d subfilters since %p has indices" %
|
488
|
+
[ expr.length, expr ]
|
489
|
+
subfilters = expr.collect {|val| Treequel::Filter.new(key, val) }
|
490
|
+
Treequel::Filter.new( :or, subfilters )
|
491
|
+
else
|
492
|
+
Treequel.logger.debug " value is a scalar; creating a single filter"
|
493
|
+
Treequel::Filter.new( key, expr )
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
if filterlist.length > 1
|
498
|
+
return Treequel::Filter::AndComponent.new( *filterlist )
|
499
|
+
else
|
500
|
+
return filterlist.first
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
|
505
|
+
### Parse a tuple of the form: [ Symbol, Object ] into a Treequel::Filter::Component
|
506
|
+
### and return it.
|
507
|
+
def self::parse_tuple_array_expression( expression )
|
508
|
+
Treequel.logger.debug "Parsing tuple Array expression %p" % [ expression ]
|
509
|
+
|
510
|
+
case expression[1]
|
511
|
+
|
512
|
+
# [ :and/:or/:not, [:uid, 1] ] := (&/|/!(uid=1))
|
513
|
+
# [ :and/:or/:not, {:uid => 1} ] := (&/|/!(uid=1))
|
514
|
+
when Array, Hash
|
515
|
+
return self.parse_logical_array_expression( *expression )
|
516
|
+
|
517
|
+
when Range
|
518
|
+
Treequel.logger.debug " two ANDed item expressions from a Range"
|
519
|
+
attribute = expression[0]
|
520
|
+
range = expression[1]
|
521
|
+
left = "#{attribute}>=#{range.begin}"
|
522
|
+
right = "#{attribute}<=#{range.exclude_end? ? range.max : range.end}"
|
523
|
+
return self.parse_logical_array_expression( :and, [left, right] )
|
524
|
+
|
525
|
+
# [ :attribute, 'value' ] := '(attribute=value)'
|
526
|
+
# when String, Symbol, Numeric, Time
|
527
|
+
else
|
528
|
+
Treequel.logger.debug " item expression from a %p" % [ expression[1].class ]
|
529
|
+
return self.parse_item_component( *expression )
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
|
534
|
+
### Break down the given +expression+ as a logical (AND, OR, or NOT)
|
535
|
+
### filter component and return it.
|
536
|
+
def self::parse_logical_array_expression( op, *components )
|
537
|
+
Treequel.logger.debug "Parsing logical %p expression with components: %p" %
|
538
|
+
[ op, components ]
|
539
|
+
|
540
|
+
compclass = LOGICAL_COMPONENTS[ op ] or
|
541
|
+
raise "don't know what a %p condition is. I only know about: %p" %
|
542
|
+
[ op, LOGICAL_COMPONENTS.keys ]
|
543
|
+
|
544
|
+
filterlist = components.collect do |filterexp|
|
545
|
+
Treequel.logger.debug " making %p into a component" % [ filterexp ]
|
546
|
+
Treequel::Filter.new( filterexp )
|
547
|
+
end.flatten
|
548
|
+
|
549
|
+
return compclass.new( *filterlist )
|
550
|
+
end
|
551
|
+
|
552
|
+
|
553
|
+
### Parse an item component from the specified +attribute+ and +value+
|
554
|
+
def self::parse_item_component( attribute, value )
|
555
|
+
Treequel.logger.debug " tuple expression (%p=%p)-> item component" %
|
556
|
+
[ attribute, value ]
|
557
|
+
|
558
|
+
case
|
559
|
+
when attribute.to_s.index( ':' )
|
560
|
+
raise NotImplementedError, "extensible filters are not yet supported"
|
561
|
+
when value == '*'
|
562
|
+
return Treequel::Filter::PresentItemComponent.new( attribute )
|
563
|
+
when value =~ LDAP_SUBSTRING_FILTER_VALUE
|
564
|
+
return Treequel::Filter::SubstringItemComponent.new( attribute, value )
|
565
|
+
else
|
566
|
+
return Treequel::Filter::SimpleItemComponent.new( attribute, value )
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
|
571
|
+
### Parse a Sequel::SQL::Expression as a Treequel::Filter::Component and return it.
|
572
|
+
def self::parse_sequel_expression( expression )
|
573
|
+
Treequel.logger.debug " parsing Sequel expression: %p" % [ expression ]
|
574
|
+
|
575
|
+
if expression.respond_to?( :op )
|
576
|
+
op = expression.op.to_s.downcase.to_sym
|
577
|
+
|
578
|
+
if equivalent = SEQUEL_FILTERTYPE_EQUIVALENTS[ op ]
|
579
|
+
attribute, value = *expression.args
|
580
|
+
|
581
|
+
# Turn :sn.like( 'bob' ) into (cn~=bob) 'cause it has no asterisks
|
582
|
+
if op == :like && value !~ /\*/
|
583
|
+
Treequel.logger.debug \
|
584
|
+
" turning a LIKE expression with no wildcards into an 'approx' filter"
|
585
|
+
equivalent = :approx
|
586
|
+
end
|
587
|
+
|
588
|
+
return Treequel::Filter::SimpleItemComponent.new( attribute, value, equivalent )
|
589
|
+
|
590
|
+
elsif op == :'!='
|
591
|
+
contents = Treequel::Filter.new( expression.args )
|
592
|
+
return Treequel::Filter::NotComponent.new( contents )
|
593
|
+
|
594
|
+
elsif op == :'not like'
|
595
|
+
equivalent = nil
|
596
|
+
attribute, value = *expression.args
|
597
|
+
Treequel.logger.debug " making a NOT LIKE expression out of: %p" % [ expression ]
|
598
|
+
|
599
|
+
if value !~ /\*/
|
600
|
+
Treequel.logger.debug \
|
601
|
+
" turning a NOT LIKE expression with no wildcards into an 'approx' filter"
|
602
|
+
equivalent = :approx
|
603
|
+
else
|
604
|
+
equivalent = :equal
|
605
|
+
end
|
606
|
+
|
607
|
+
comp = Treequel::Filter::SimpleItemComponent.new( attribute, value, equivalent )
|
608
|
+
filter = Treequel::Filter.new( comp )
|
609
|
+
return Treequel::Filter::NotComponent.new( filter )
|
610
|
+
|
611
|
+
elsif LOGICAL_COMPONENTS.key?( op )
|
612
|
+
components = expression.args.collect do |comp|
|
613
|
+
Treequel::Filter.new( comp )
|
614
|
+
end
|
615
|
+
|
616
|
+
return self.parse_logical_array_expression( op, components )
|
617
|
+
|
618
|
+
elsif msg = UNSUPPORTED_SEQUEL_FILTERTYPES[ op ]
|
619
|
+
raise Treequel::ExpressionError,
|
620
|
+
"unsupported Sequel filter syntax %p: %s" %
|
621
|
+
[ expression, msg ]
|
622
|
+
else
|
623
|
+
raise ScriptError,
|
624
|
+
" unhandled Sequel BooleanExpression: add handling for %p: %p" % [ op, expression ]
|
625
|
+
end
|
626
|
+
|
627
|
+
else
|
628
|
+
raise Treequel::ExpressionError,
|
629
|
+
"don't know how to turn %p into a component" % [ expression ]
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
|
634
|
+
#################################################################
|
635
|
+
### I N S T A N C E M E T H O D S
|
636
|
+
#################################################################
|
637
|
+
|
638
|
+
### Create a new Treequel::Branchset::Filter with the specified +expression+.
|
639
|
+
def initialize( *expression_parts )
|
640
|
+
@component = self.class.parse_expression( expression_parts )
|
641
|
+
self.log.debug "created a filter with component: %p" % [ @component ]
|
642
|
+
|
643
|
+
super()
|
644
|
+
end
|
645
|
+
|
646
|
+
|
647
|
+
######
|
648
|
+
public
|
649
|
+
######
|
650
|
+
|
651
|
+
# The filtercomp part of the filter
|
652
|
+
attr_accessor :component
|
653
|
+
|
654
|
+
|
655
|
+
### Return the Treequel::Branchset::Filter as a String.
|
656
|
+
def to_s
|
657
|
+
# self.log.debug "stringifying filter %p" % [ self ]
|
658
|
+
filtercomp = self.component.to_s
|
659
|
+
if filtercomp[0] == ?(
|
660
|
+
return filtercomp
|
661
|
+
else
|
662
|
+
return '(' + filtercomp + ')'
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
|
667
|
+
### Return a human-readable string representation of the filter suitable
|
668
|
+
### for debugging.
|
669
|
+
def inspect
|
670
|
+
return %{#<%s:0x%0x (%s)>} % [
|
671
|
+
self.class.name,
|
672
|
+
self.object_id * 2,
|
673
|
+
self.component,
|
674
|
+
]
|
675
|
+
end
|
676
|
+
|
677
|
+
|
678
|
+
### Returns +true+ if the filter contains a single 'present' component for
|
679
|
+
### the objectClass attribute (which will match every entry)
|
680
|
+
def promiscuous?
|
681
|
+
return self.component.promiscuous?
|
682
|
+
end
|
683
|
+
alias_method :is_promiscuous?, :promiscuous?
|
684
|
+
|
685
|
+
|
686
|
+
### Equality operator -- returns +true+ if +other_filter+ is equivalent
|
687
|
+
### to the receiver.
|
688
|
+
def ==( other_filter )
|
689
|
+
return ( self.component == other_filter.component )
|
690
|
+
end
|
691
|
+
|
692
|
+
|
693
|
+
### AND two filters together
|
694
|
+
def &( other_filter )
|
695
|
+
return other_filter if self.promiscuous?
|
696
|
+
return self.dup if other_filter.promiscuous?
|
697
|
+
return self.class.new( :and, [self, other_filter] )
|
698
|
+
end
|
699
|
+
alias_method :+, :&
|
700
|
+
|
701
|
+
|
702
|
+
|
703
|
+
end # class Treequel::Filter
|
704
|
+
|