treequel 1.3.0pre384 → 1.3.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.
data/ChangeLog CHANGED
@@ -1,8 +1,53 @@
1
+ 2011-01-12 Michael Granger <ged@FaerieMUD.org>
2
+
3
+ * lib/treequel/model.rb:
4
+ Always initialize the dirty flag for Treequel::Model objects.
5
+ [4a75d892a89d] [github/master, master, tip]
6
+
7
+ * lib/treequel/branch.rb, spec/treequel/branch_spec.rb:
8
+ Fixed a bug in Treequel::Branch that caused non-schema attribute
9
+ fetches through the index operator to pollute the values cache,
10
+ which in turn caused Treequel::Model schema validations to fail.
11
+ [8cee4f66c05d]
12
+
13
+ * manual/src/models.page:
14
+ More work on the manual
15
+ [1652fd0874ab]
16
+
17
+ * lib/treequel/model.rb, spec/treequel/model_spec.rb:
18
+ Made Treequel::Model#validate run the
19
+ before_validation/after_validation hooks
20
+ [55d6b5f34c2b]
21
+
22
+ 2011-01-11 Michael Granger <ged@FaerieMUD.org>
23
+
24
+ * lib/treequel/exceptions.rb, lib/treequel/model.rb,
25
+ manual/src/models.page, spec/treequel/model_spec.rb:
26
+ Worked on model hooks and validations; added specs, raise exceptions
27
+ by default, started adding info to the manual.
28
+ [2454e05f1302]
29
+
30
+ * lib/treequel/model/errors.rb,
31
+ lib/treequel/model/schemavalidations.rb,
32
+ spec/treequel/model/errors_spec.rb,
33
+ spec/treequel/model/schemavalidations_spec.rb:
34
+ Adding structural-objectClass validation
35
+ [1b9bd6587cf0]
36
+
37
+ * Rakefile:
38
+ Add sysexits to the list of dev dependencies
39
+ [54e20f994ef4]
40
+
41
+ * Manifest.txt:
42
+ Rearranged the release manifest a bit to make it easier to check
43
+ against the source in TextMate
44
+ [fd358623589a]
45
+
1
46
  2011-01-10 Michael Granger <ged@FaerieMUD.org>
2
47
 
3
48
  * Rakefile:
4
49
  Cleaning up dependencies, adding a note to the post-install
5
- [633dfb8ea77a] [github/master, master, tip]
50
+ [633dfb8ea77a]
6
51
 
7
52
  * Manifest.txt:
8
53
  Adding the ChangeLog to the release manifest
data/History.md CHANGED
@@ -1,4 +1,4 @@
1
- ## 1.3.0 [2011-01-07] Michael Granger <ged@FaerieMUD.org>
1
+ ## 1.3.0 [2011-01-13] Michael Granger <ged@FaerieMUD.org>
2
2
 
3
3
  Enhancements:
4
4
 
@@ -12,6 +12,17 @@ Enhancements:
12
12
  - Treequel::Model#errors
13
13
  - Treequel::Model#revert
14
14
  - Treequel::Model#modified?
15
+ - Treequel::Model#after_initialize
16
+ - Treequel::Model#before_validation
17
+ - Treequel::Model#after_validation
18
+ - Treequel::Model#before_save
19
+ - Treequel::Model#before_create
20
+ - Treequel::Model#before_update
21
+ - Treequel::Model#after_create
22
+ - Treequel::Model#after_update
23
+ - Treequel::Model#after_save
24
+ - Treequel::Model#before_destroy
25
+ - Treequel::Model#after_destroy
15
26
  New classes:
16
27
  - Treequel::Model::Errors
17
28
  - Treequel::ValidationFailed
@@ -36,6 +47,7 @@ Bugfixes:
36
47
  - Check for explicit nil DN in .new
37
48
  - Check for nil parent_dn in #parent
38
49
  - Use 'top' instead of :top as objectClass default
50
+ - Don't cache attempts to fetch invalid attributes
39
51
 
40
52
 
41
53
  ## 1.2.2 [2010-12-14] Michael Granger <ged@FaerieMUD.org>
data/Rakefile CHANGED
@@ -24,6 +24,7 @@ hoespec = Hoe.spec 'treequel' do
24
24
  'ruby-termios' => '~> 0.9.6',
25
25
  'ruby-terminfo' => '~> 0.1.1',
26
26
  'columnize' => '~> 0.3.1',
27
+ 'sysexits' => '~> 1.0.2',
27
28
  }
28
29
 
29
30
  self.spec_extras[:licenses] = ["BSD"]
@@ -320,7 +320,7 @@ class Treequel::Branch
320
320
  value.freeze if
321
321
  self.class.freeze_converted_values? &&
322
322
  value.respond_to?( :freeze )
323
- @values[ attrsym ] = value
323
+ @values[ attrsym ] = value if value
324
324
  end
325
325
 
326
326
  return @values[ attrsym ]
@@ -40,9 +40,34 @@ module Treequel
40
40
  end
41
41
  end
42
42
 
43
+ ######
44
+ public
45
+ ######
46
+
43
47
  # @return [Treequel::Model::Errors] the validation errors
44
48
  attr_reader :errors
45
- end
49
+
50
+ end # class ValidationFailed
51
+
52
+ ### Exception class raised when a before_* hooks returns a false value when saving
53
+ ### or destroying a Treequel::Model object.
54
+ class BeforeHookFailed < Treequel::ModelError
55
+
56
+ ### Create a new Treequel::BeforeHookFailed exception that indicates that the
57
+ ### specified +hook+ returned a false value.
58
+ def initialize( hook )
59
+ @hook = hook.to_sym
60
+ super "The 'before' hook failed when trying to %s" % [ hook ]
61
+ end
62
+
63
+ ######
64
+ public
65
+ ######
66
+
67
+ # The hook that failed
68
+ attr_reader :hook
69
+
70
+ end # class BeforeHookFailed
46
71
 
47
72
  end # module Treequel
48
73
 
@@ -47,11 +47,11 @@ class Treequel::Model::Errors < ::Hash
47
47
  end
48
48
 
49
49
 
50
- ### Adds an error for the given +attribute+.
51
- ### @param [Symbol, #to_sym] attribute the attribute with an error
50
+ ### Adds an error for the given +subject+.
51
+ ### @param [Symbol, #to_sym] subject the subject of the error
52
52
  ### @param [String] message the description of the error condition
53
- def add( attribute, message )
54
- self[ attribute ] << message
53
+ def add( subject, message )
54
+ self[ subject ] << message
55
55
  end
56
56
 
57
57
 
@@ -12,12 +12,21 @@ module Treequel::Model::SchemaValidations
12
12
  def validate( options={} )
13
13
  return unless options[:with_schema]
14
14
 
15
+ self.validate_structural_objectclass
15
16
  self.validate_must_attributes
16
17
  self.validate_may_attributes
17
18
  self.validate_attribute_syntax
18
19
  end
19
20
 
20
21
 
22
+ ### Ensure that the object has at least one structural objectClass.
23
+ def validate_structural_objectclass
24
+ unless self.object_classes.any? {|oc| oc.structural? }
25
+ self.errors.add( :entry, "must have at least one structural objectClass" )
26
+ end
27
+ end
28
+
29
+
21
30
  ### Validate that all attributes that MUST be included according to the entry's
22
31
  ### objectClasses have at least one value.
23
32
  def validate_must_attributes
@@ -53,6 +53,16 @@ class Treequel::Model < Treequel::Branch
53
53
  :with_schema => true,
54
54
  }
55
55
 
56
+ # Defaults for #save options
57
+ DEFAULT_SAVE_OPTIONS = {
58
+ :raise_on_failure => true,
59
+ }
60
+
61
+ # Defaults for #destroy options
62
+ DEFAULT_DESTROY_OPTIONS = {
63
+ :raise_on_failure => true,
64
+ }
65
+
56
66
 
57
67
  #################################################################
58
68
  ### C L A S S M E T H O D S
@@ -191,6 +201,7 @@ class Treequel::Model < Treequel::Branch
191
201
  def initialize( directory, dn, entry=nil, from_directory=false )
192
202
  if from_directory
193
203
  super( directory, dn, entry )
204
+ @dirty = false
194
205
  else
195
206
  super( directory, dn )
196
207
  @values = entry ? symbolify_keys( entry ) : {}
@@ -218,6 +229,7 @@ class Treequel::Model < Treequel::Branch
218
229
  HOOKS.each do |hook|
219
230
  define_method( hook ) do |*args|
220
231
  self.log.debug "#{hook} default hook called."
232
+ true
221
233
  end
222
234
  end
223
235
 
@@ -321,31 +333,36 @@ class Treequel::Model < Treequel::Branch
321
333
  ### Return +true+ if the model object passes all of its validations.
322
334
  def valid?( opts={} )
323
335
  self.errors.clear
324
-
325
- return false if self.before_validation == false
326
- self.validate
327
- self.after_validation
328
-
336
+ self.validate( opts )
329
337
  return self.errors.empty? ? true : false
330
338
  end
331
339
 
332
340
 
333
- ### Validate the object with the specified +options+.
341
+ ### Validate the object with the specified +options+. Appending validation errors onto
342
+ ### the #errors object.
334
343
  ### @param [Hash] options options for validation.
335
344
  ### @option options [Boolean] :with_schema whether or not to run the schema validations
336
345
  def validate( options={} )
337
346
  options = DEFAULT_VALIDATION_OPTIONS.merge( options )
338
347
 
348
+ self.before_validation or
349
+ raise Treequel::BeforeHookFailed, :validation
339
350
  self.errors.add( :objectClass, 'must have at least one' ) if self.object_classes.empty?
340
351
 
341
352
  super( options )
353
+ self.after_validation
342
354
  end
343
355
 
344
356
 
345
357
  ### Write any pending changes in the model object to the directory.
346
- def save
358
+ ### @param [Hash] opts save options
359
+ ### @option opts [Boolean] :raise_on_failure (true) raise a Treequel::ValidationFailed or
360
+ ### Treequel::BeforeHookFailed if either the validations or before_{save,create}
361
+ def save( opts={} )
362
+ opts = DEFAULT_SAVE_OPTIONS.merge( opts )
363
+
347
364
  self.log.debug "Saving %s..." % [ self.dn ]
348
- raise Treequel::ValidationFailed, self.errors unless self.valid?
365
+ raise Treequel::ValidationFailed, self.errors unless self.valid?( opts )
349
366
  self.log.debug " validation succeeded."
350
367
 
351
368
  unless mods = self.modifications
@@ -354,24 +371,25 @@ class Treequel::Model < Treequel::Branch
354
371
  end
355
372
 
356
373
  self.log.debug " got %d modifications." % [ mods.length ]
357
- self.before_save( mods ) or return nil
374
+ self.before_save( mods ) or
375
+ raise Treequel::BeforeHookFailed, :save
358
376
 
359
377
  if self.exists?
360
- self.log.debug " entry already exists: updating..."
361
- self.before_update( mods ) or return nil
362
- self.modify( mods )
363
- self.after_update( mods )
364
-
378
+ self.update( mods )
365
379
  else
366
- self.log.debug " entry doesn't exist: creating..."
367
- self.before_create( mods ) or return nil
368
380
  self.create( mods )
369
- self.after_create( mods )
370
381
  end
371
382
 
372
383
  self.after_save( mods )
373
384
 
374
385
  return true
386
+ rescue Treequel::BeforeHookFailed => err
387
+ self.log.info( err.message )
388
+ raise if opts[:raise_on_failure]
389
+ rescue Treequel::ValidationFailed => err
390
+ self.log.error( "Save aborted: validation failed." )
391
+ self.log.info( err.errors.full_messages.join(', ') )
392
+ raise if opts[:raise_on_failure]
375
393
  end
376
394
 
377
395
 
@@ -437,6 +455,21 @@ class Treequel::Model < Treequel::Branch
437
455
  end
438
456
 
439
457
 
458
+ ### Like #delete, but runs destroy hooks before and after deleting.
459
+ def destroy( opts={} )
460
+ opts = DEFAULT_DESTROY_OPTIONS.merge( opts )
461
+
462
+ self.before_destroy or raise Treequel::BeforeHookFailed, :destroy
463
+ self.delete
464
+ self.after_destroy
465
+
466
+ return true
467
+ rescue Treequel::BeforeHookFailed => err
468
+ self.log.info( err.message )
469
+ raise if opts[:raise_on_failure]
470
+ end
471
+
472
+
440
473
  ### Override Branch#search to inject the 'objectClass' attribute to the
441
474
  ### selected attribute list if there is one.
442
475
  def search( scope=:subtree, filter='(objectClass=*)', parameters={}, &block )
@@ -493,6 +526,28 @@ class Treequel::Model < Treequel::Branch
493
526
  end
494
527
 
495
528
 
529
+ ### Update the object's entry with the specified +mods+.
530
+ ### @param [Array<LDAP::Mod>] mods the modifications to make
531
+ def update( mods )
532
+ self.log.debug " entry already exists: updating..."
533
+ self.before_update( mods ) or
534
+ raise Treequel::BeforeHookFailed, :update
535
+ self.modify( mods )
536
+ self.after_update( mods )
537
+ end
538
+
539
+
540
+ ### Create the entry for the object, using the specified +mods+ to set the attributes.
541
+ ### @param [Array<LDAP::Mod>] mods the modifications to set attributes
542
+ def create( mods )
543
+ self.log.debug " entry doesn't exist: creating..."
544
+ self.before_create( mods ) or
545
+ raise Treequel::BeforeHookFailed, :create
546
+ super( mods )
547
+ self.after_create( mods )
548
+ end
549
+
550
+
496
551
  ### Delete specific key/value +pairs+ from the entry.
497
552
  ### @param [Hash] pairs key/value pairs to delete from the entry.
498
553
  def delete_specific_values( pairs )
@@ -801,6 +801,15 @@ describe Treequel::Branch do
801
801
  2.times { @branch[ :description ] }
802
802
  end
803
803
 
804
+ it "doesn't cache nil values that don't correspond to an attribute type in the schema" do
805
+ @conn.should_receive( :search_ext2 ).
806
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
807
+ and_return([ @entry ])
808
+
809
+ @branch[ :string_beans ]
810
+ @branch.instance_variable_get( :@values ).should_not have_key( :string_beans )
811
+ end
812
+
804
813
  it "freezes the values fetched from its entry by default to prevent accidental " +
805
814
  "in-place modification" do
806
815
  @conn.should_receive( :search_ext2 ).
@@ -47,8 +47,8 @@ describe Treequel::Model::Errors do
47
47
 
48
48
  it "knows how many errors there are" do
49
49
  @errors.add( :l, "is not valid" )
50
- @errors.add( :description, "must " )
51
- @errors.add( :description, "must have at least one description" )
50
+ @errors.add( :description, "must be this tall to ride" )
51
+ @errors.add( :description, "must have at least one value" )
52
52
 
53
53
  @errors.count.should == 3
54
54
  end
@@ -45,10 +45,23 @@ describe Treequel::Model::SchemaValidations do
45
45
  # MUST ( sn $ cn )
46
46
  # MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )
47
47
 
48
+ it "adds an error if the object doesn't have at least one structural objectClass" do
49
+ @conn.stub( :search_ext2 ).and_return( [] )
50
+ @modelobj.object_class = ['ipHost', 'ieee802device']
51
+ @modelobj.cn = 'testhost'
52
+ @modelobj.ip_host_number = '127.0.0.1'
53
+
54
+ @modelobj.should_not be_valid()
55
+ @modelobj.errors.should have( 1 ).member
56
+ @modelobj.errors.full_messages.
57
+ should include( 'entry must have at least one structural objectClass' )
58
+ end
59
+
48
60
  it "adds an error if the object doesn't have at least one value for all of its MUST attributes" do
49
61
  @conn.stub( :search_ext2 ).and_return( [] )
50
62
  @modelobj.object_class = 'person'
51
- @modelobj.validate
63
+
64
+ @modelobj.should_not be_valid()
52
65
  @modelobj.errors.should have( 2 ).members
53
66
  @modelobj.errors.full_messages.should include( 'cn MUST have at least one value' )
54
67
  @modelobj.errors.full_messages.should include( 'sn MUST have at least one value' )
@@ -66,7 +79,7 @@ describe Treequel::Model::SchemaValidations do
66
79
 
67
80
  # ..then remove the objectclass that grants it and validate
68
81
  @modelobj.object_class -= ['inetOrgPerson']
69
- @modelobj.validate
82
+ @modelobj.should_not be_valid()
70
83
 
71
84
  @modelobj.errors.full_messages.
72
85
  should == [%{displayName is not allowed by entry's objectClasses}]
@@ -92,7 +105,7 @@ describe Treequel::Model::SchemaValidations do
92
105
  @modelobj.uid_number = "something that's not a number"
93
106
  @modelobj.gid_number = "also not a number"
94
107
 
95
- @modelobj.validate
108
+ @modelobj.should_not be_valid()
96
109
 
97
110
  @modelobj.errors.should have( 2 ).members
98
111
  @modelobj.errors.full_messages.should include( "uidNumber isn't a valid Integer value" )
@@ -446,7 +446,53 @@ describe Treequel::Model do
446
446
  end
447
447
 
448
448
  it "knows if any validation errors have been encountered" do
449
- @obj.errors.should be_a( Hash )
449
+ @obj.errors.should be_a( Treequel::Model::Errors )
450
+ end
451
+
452
+ it "can delete its entry with callbacks" do
453
+ class << @obj; attr_reader :callbacks; end
454
+ @obj.instance_variable_set( :@callbacks, [] )
455
+ def @obj.before_destroy
456
+ self.callbacks << :before_destroy
457
+ end
458
+ def @obj.after_destroy
459
+ self.callbacks << :after_destroy
460
+ end
461
+
462
+ @conn.should_receive( :delete ).with( @obj.dn )
463
+
464
+ @obj.destroy
465
+ @obj.callbacks.should == [ :before_destroy, :after_destroy ]
466
+ end
467
+
468
+ it "doesn't delete its entry if the destroy callback returns something falseish" do
469
+ def @obj.before_destroy
470
+ false
471
+ end
472
+ def @obj.after_destroy
473
+ fail "shouldn't call the after_destroy hook, either"
474
+ end
475
+
476
+ @conn.should_not_receive( :delete )
477
+
478
+ expect {
479
+ @obj.destroy
480
+ }.to raise_error( Treequel::BeforeHookFailed, /destroy/i )
481
+ end
482
+
483
+ it "doesn't raise a BeforeHookFailed if destroyed without :raise_on_failure" do
484
+ def @obj.before_destroy
485
+ false
486
+ end
487
+
488
+ @conn.should_not_receive( :delete )
489
+
490
+ result = nil
491
+ expect {
492
+ result = @obj.destroy( :raise_on_failure => false )
493
+ }.to_not raise_error()
494
+
495
+ result.should be_false()
450
496
  end
451
497
 
452
498
 
@@ -501,6 +547,54 @@ describe Treequel::Model do
501
547
  @obj.save
502
548
  end
503
549
 
550
+
551
+ it "calls update hooks when saved" do
552
+ class << @obj; attr_reader :callbacks; end
553
+ @obj.instance_variable_set( :@callbacks, [] )
554
+ def @obj.before_update( mods )
555
+ self.callbacks << :before_update
556
+ end
557
+ def @obj.after_update( mods )
558
+ self.callbacks << :after_update
559
+ end
560
+
561
+ @conn.should_receive( :modify )
562
+
563
+ @obj.save
564
+ @obj.callbacks.should == [ :before_update, :after_update ]
565
+ end
566
+
567
+ it "doesn't modify its entry if the before_update callback returns something falseish" do
568
+ def @obj.before_update( mods )
569
+ false
570
+ end
571
+ def @obj.after_update( mods )
572
+ fail "shouldn't call the after_update hook, either"
573
+ end
574
+
575
+ @conn.should_not_receive( :modify )
576
+
577
+ expect {
578
+ @obj.save
579
+ }.to raise_error( Treequel::BeforeHookFailed, /update/i )
580
+ end
581
+
582
+ it "doesn't raise a BeforeHookFailed if saved without :raise_on_failure" do
583
+ def @obj.before_update( mods )
584
+ false
585
+ end
586
+
587
+ @conn.stub( :search_ext2 ).with( @obj.dn, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
588
+ and_return( [] )
589
+ @conn.should_not_receive( :modify )
590
+
591
+ result = nil
592
+ expect {
593
+ result = @obj.save( :raise_on_failure => false )
594
+ }.to_not raise_error()
595
+
596
+ result.should be_false()
597
+ end
504
598
  end
505
599
 
506
600
 
@@ -593,11 +687,11 @@ describe Treequel::Model do
593
687
 
594
688
  it "creates the entry in the directory when saved" do
595
689
  @conn.stub( :search_ext2 ).
596
- with( TEST_PERSON_DN, LDAP::LDAP_SCOPE_BASE, '(objectClass=*)').
690
+ with( @obj.dn, LDAP::LDAP_SCOPE_BASE, '(objectClass=*)').
597
691
  and_return( [] )
598
692
 
599
693
  @conn.should_receive( :add ).
600
- with( TEST_PERSON_DN, [
694
+ with( @obj.dn, [
601
695
  ldap_mod_add("cn", "James"),
602
696
  ldap_mod_add("displayName", "J. Random Hacker"),
603
697
  ldap_mod_add("gidNumber", "200"),
@@ -612,6 +706,58 @@ describe Treequel::Model do
612
706
  @obj.save
613
707
  end
614
708
 
709
+ it "calls creation hooks when saved" do
710
+ class << @obj; attr_reader :callbacks; end
711
+ @obj.instance_variable_set( :@callbacks, [] )
712
+ def @obj.before_create( mods )
713
+ self.callbacks << :before_create
714
+ end
715
+ def @obj.after_create( mods )
716
+ self.callbacks << :after_create
717
+ end
718
+
719
+ @conn.stub( :search_ext2 ).with( @obj.dn, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
720
+ and_return( [] )
721
+ @conn.should_receive( :add )
722
+
723
+ @obj.save
724
+ @obj.callbacks.should == [ :before_create, :after_create ]
725
+ end
726
+
727
+ it "doesn't add its entry if the before_create callback returns something falseish" do
728
+ def @obj.before_create( mods )
729
+ false
730
+ end
731
+ def @obj.after_create( mods )
732
+ fail "shouldn't call the after_create hook, either"
733
+ end
734
+
735
+ @conn.stub( :search_ext2 ).with( @obj.dn, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
736
+ and_return( [] )
737
+ @conn.should_not_receive( :add )
738
+
739
+ expect {
740
+ @obj.save
741
+ }.to raise_error( Treequel::BeforeHookFailed, /create/i )
742
+ end
743
+
744
+ it "doesn't raise a BeforeHookFailed if saved without :raise_on_failure" do
745
+ def @obj.before_create( mods )
746
+ false
747
+ end
748
+
749
+ @conn.stub( :search_ext2 ).with( @obj.dn, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
750
+ and_return( [] )
751
+ @conn.should_not_receive( :add )
752
+
753
+ result = nil
754
+ expect {
755
+ result = @obj.save( :raise_on_failure => false )
756
+ }.to_not raise_error()
757
+
758
+ result.should be_false()
759
+ end
760
+
615
761
  end
616
762
 
617
763
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -7,7 +7,7 @@ version: !ruby/object:Gem::Version
7
7
  - 1
8
8
  - 3
9
9
  - 0
10
- version: 1.3.0pre384
10
+ version: 1.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Michael Granger
@@ -36,7 +36,7 @@ cert_chain:
36
36
  cmlhXe46pZNJgWKbxZah85jIjx95hR8vOI+NAM5iH9kOqK13DrxacTKPhqj5PjwF
37
37
  -----END CERTIFICATE-----
38
38
 
39
- date: 2011-01-10 00:00:00 -08:00
39
+ date: 2011-01-13 00:00:00 -08:00
40
40
  default_executable:
41
41
  dependencies:
42
42
  - !ruby/object:Gem::Dependency
@@ -136,9 +136,25 @@ dependencies:
136
136
  type: :development
137
137
  version_requirements: *id006
138
138
  - !ruby/object:Gem::Dependency
139
- name: hoe
139
+ name: sysexits
140
140
  prerelease: false
141
141
  requirement: &id007 !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ~>
145
+ - !ruby/object:Gem::Version
146
+ hash: 19
147
+ segments:
148
+ - 1
149
+ - 0
150
+ - 2
151
+ version: 1.0.2
152
+ type: :development
153
+ version_requirements: *id007
154
+ - !ruby/object:Gem::Dependency
155
+ name: hoe
156
+ prerelease: false
157
+ requirement: &id008 !ruby/object:Gem::Requirement
142
158
  none: false
143
159
  requirements:
144
160
  - - ">="
@@ -150,7 +166,7 @@ dependencies:
150
166
  - 0
151
167
  version: 2.8.0
152
168
  type: :development
153
- version_requirements: *id007
169
+ version_requirements: *id008
154
170
  description: |-
155
171
  Treequel is an LDAP toolkit for Ruby. It is intended to allow quick, easy
156
172
  access to LDAP directories in a manner consistent with LDAP's hierarchical,
metadata.gz.sig CHANGED
Binary file