gibbler 0.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.txt CHANGED
@@ -1,9 +1,23 @@
1
1
  GIBBLER, CHANGES
2
2
 
3
3
 
4
+ #### 0.5 (2009-07-01) #################################
5
+
6
+ NOTE: This is a significant change from 0.4. Many method names
7
+ have been modified so this release is not backwards compatible.
8
+
9
+ * CHANGE: Now refer to "gibble" as "digest" in all docs and methods.
10
+ * CHANGE: Gibbler#gibble -> Gibbler#gibbler
11
+ * CHANGE: Gibble is now Gibbler::Digest
12
+ * ADDED: Gibbler::History, supporting gibbler_snapshots and gibbler_revert
13
+ for the following objects: Array, Hash
14
+ * ADDED: Support for short, 8-character digests
15
+ * ADDED: Expanded test coverage
16
+
17
+
4
18
  #### 0.4 (2009-06-30) #################################
5
19
 
6
- NOTE: Calculated gibbles have changed since 0.3. Most gibbles created with
20
+ NOTE: Calculated digests have changed since 0.3. Most digests created with
7
21
  0.3 and earlier will not match those created in 0.4 for the same object
8
22
 
9
23
  * FIXED: Hash and Array now use the class of the value for hashing
data/README.rdoc CHANGED
@@ -1,25 +1,64 @@
1
- = Gibbler - v0.4
1
+ = Gibbler - v0.5 ALPHA
2
2
 
3
- Git-like hashes for Ruby objects.
3
+ Git-like hashes and history for Ruby objects.
4
4
 
5
- == Examples
6
-
7
- config = {}
8
- config.gibble # => 4fdcadc66a38feb9c57faf3c5a18d5e76a6d29bf
9
- config.gibbled? # => false
5
+ == Example 1 -- Basic Usage
10
6
 
11
- config[:server] = {
12
- :users => [:dave, :ali],
13
- :ports => [22, 80, 443]
14
- }
15
- config.gibbled? # => true
16
- config.gibble # => ef23d605f8c4fc80a8e580f9a0e8dab8426454a8
7
+ require 'gibbler'
8
+
9
+ config = {}
10
+ config.gibbler # => 4fdcadc66a38feb9c57faf3c5a18d5e76a6d29bf
11
+ config.gibbled? # => false
12
+
13
+ config[:server] = {
14
+ :users => [:dave, :ali],
15
+ :ports => [22, 80, 443]
16
+ }
17
+ config.gibbled? # => true
18
+ config.gibbler # => ef23d605f8c4fc80a8e580f9a0e8dab8426454a8
17
19
 
18
20
  config[:server][:users] << :yanni
19
21
 
20
- config.gibble # => 4c558a56bc2abf5f8a845a69e47ceb5e0003683f
22
+ config.gibbler # => 4c558a56bc2abf5f8a845a69e47ceb5e0003683f
23
+
24
+ config.gibbler.short # => 4c558a56
25
+
21
26
 
27
+ == Example 2 -- Object History
28
+
29
+ Gibbler can also keep track of the history of changes to an object. By default Gibbler supports history for Hash, Array, and String objects. The <tt>gibbler_commit</tt> method creates a clone of the current object and stores in an instance variable using the current hash digest as the key.
22
30
 
31
+ require 'gibbler'
32
+ require 'gibbler/history'
33
+
34
+ a = { :magic => :original }
35
+ a.gibbler_commit # => d7049916ddb25e6cc438b1028fb957e5139f9910
36
+
37
+ a[:magic] = :updated
38
+ a.gibbler_commit # => b668098e16d08898532bf3aa33ce2253a3a4150e
39
+
40
+ a[:magic] = :changed
41
+ a.gibbler_commit # => 0b11c377fccd44554a601e5d2b135c46dc1c4cb1
42
+
43
+ a.gibbler_history # => d7049916, b668098e, 0b11c377
44
+
45
+ a.gibbler_revert 'd7049916' # Return to a specific commit
46
+ a.gibbler # => d7049916ddb25e6cc438b1028fb957e5139f9910
47
+ a # => { :magic => :original }
48
+
49
+ a.delete :magic
50
+
51
+ a.gibbler_revert # Return to the previous commit
52
+ a.gibbler # => 0b11c377fccd44554a601e5d2b135c46dc1c4cb1
53
+ a # => { :magic => :changed }
54
+
55
+
56
+ a.gibbler_object 'b668098e' # => { :magic => :updated }
57
+ a.gibbler_stamp # => 2009-07-01 18:56:52 -0400
58
+
59
+ http://delano.github.com/gibbler/img/whoababy.gif
60
+
61
+
23
62
  == Supported Classes
24
63
 
25
64
  Gibbler methods are available only to the classes which explicitly include them (see RDocs[http://delano.github.com/gibbler] for details on which classes are supported by default). You can also extend custom objects:
@@ -30,10 +69,10 @@ Gibbler methods are available only to the classes which explicitly include them
30
69
  end
31
70
 
32
71
  a = FullHouse.new
33
- a.gibble # => 4192d4cb59975813f117a51dcd4454ac16df6703
72
+ a.gibbler # => 4192d4cb59975813f117a51dcd4454ac16df6703
34
73
 
35
74
  a.roles = [:jesse, :joey, :danny, :kimmy, :michelle, :dj, :stephanie]
36
- a.gibble # => 6ea546919dc4caa2bab69799b71d48810a1b48fa
75
+ a.gibbler # => 6ea546919dc4caa2bab69799b71d48810a1b48fa
37
76
 
38
77
  Gibbler::Complex creates a digest based on the name of the class and the names and values of the instance variables. See the RDocs[http://delano.github.com/gibbler] for other Gibbler::* types.
39
78
 
@@ -43,19 +82,31 @@ If you want to support all Ruby objects, add the following to your application:
43
82
  include Gibbler::String
44
83
  end
45
84
 
46
- Gibbler::String creates a digest based on the output of the to_s method. This is a reasonable default for more objects however any object that includes the object address in to_s (e.g. "<Object:0x0x4ac9f0...") will produce unreliable gibbles (because the address can change).
85
+ Gibbler::String creates a digest based on the name of the class and the output of the to_s method. This is a reasonable default for most objects however any object that includes the object address in to_s (e.g. "<Object:0x0x4ac9f0...") will produce unreliable digests (because the address can change).
86
+
87
+
88
+ == ALPHA Notice
89
+
90
+ This code is hella fresh (est 2009-06-25). It's barely tested and the interface may change, but it's fun to play with. I'll happily accept patches.
91
+
92
+ NOTE: Gibbler history is not suitable for very large objects since it keeps complete copies of the object in memory. This is a very early implementation of this feature so don't rely on it for production code.
93
+
94
+
95
+ == News
96
+
97
+ === 2009-07-01: Major namespace reorganization
47
98
 
99
+ Many method names have been modified so this release is not backwards compatible with previous releases. This clears up ambiguity regarding "gibbles". They're now rightly referred to as digests.
48
100
 
49
- == ALPHA NOTICE (2009-06-30)
50
101
 
51
- This code is hella fresh. It's ugly and barely tested, but it's fun to play with. I'll happily accept patches.
102
+ === 2009-06-30: Digests have changed in 0.4
52
103
 
53
- NOTE: Gibbles have changed between 0.3 and 0.4. Ones created with 0.3 and earlier will not match ones created with 0.4 for the same object.
104
+ Digests have changed between 0.3 and 0.4. Ones created with 0.3 and earlier will not match ones created with 0.4 for the same object.
54
105
 
55
106
 
56
107
  == Known Issues
57
108
 
58
- * The gibble method or gibbled? must be called at least once before gibbled? will be able to return a useful value (otherwise there is no previous gibble value to compare to)
109
+ * gibbler or gibbled? must be called at least once before gibbled? will be able to return a useful value (otherwise there is no previous digest value to compare to)
59
110
 
60
111
 
61
112
  == More Info
data/gibbler.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  @spec = Gem::Specification.new do |s|
2
2
  s.name = "gibbler"
3
3
  s.rubyforge_project = "gibbler"
4
- s.version = "0.4"
4
+ s.version = "0.5.0"
5
5
  s.summary = "Gibbler: Git-like hashes for Ruby objects"
6
6
  s.description = s.summary
7
7
  s.author = "Delano Mandelbaum"
@@ -25,12 +25,28 @@
25
25
 
26
26
  # = MANIFEST =
27
27
  s.files = %w(
28
- README.rdoc
29
28
  CHANGES.txt
30
29
  LICENSE.txt
30
+ README.rdoc
31
31
  Rakefile
32
32
  gibbler.gemspec
33
33
  lib/gibbler.rb
34
+ lib/gibbler/digest.rb
35
+ lib/gibbler/history.rb
36
+ lib/gibbler/mixins.rb
37
+ lib/gibbler/mixins/string.rb
38
+ tryouts/01_mixins_tryouts.rb
39
+ tryouts/05_gibbler_digest_tryouts.rb
40
+ tryouts/10_basic_tryouts.rb
41
+ tryouts/11_basic_sha256_tryouts.rb
42
+ tryouts/50_history_tryouts.rb
43
+ tryouts/51_hash_history_tryouts.rb
44
+ tryouts/52_array_history_tryouts.rb
45
+ tryouts/53_string_history_tryouts.rb
46
+ tryouts/57_arbitrary_history_tryouts.rb
47
+ tryouts/59_history_exceptions_tryouts.rb
48
+ tryouts/80_performance_tryouts.rb
49
+ tryouts/object_hash_demo.rb
34
50
  )
35
51
 
36
52
  s.has_rdoc = true
data/lib/gibbler.rb CHANGED
@@ -6,10 +6,17 @@ require 'digest/sha1'
6
6
  # "Hola, Tanneritos"
7
7
  #
8
8
  module Gibbler
9
- VERSION = "0.4.0"
9
+ VERSION = "0.5.0"
10
+
11
+ require 'gibbler/digest'
12
+ require 'gibbler/mixins'
13
+
14
+ class Error < RuntimeError
15
+ def initialize(obj); @obj = obj; end
16
+ end
10
17
 
11
18
  @@gibbler_debug = false
12
- @@gibbler_digest_type = Digest::SHA1
19
+ @@gibbler_digest_type = ::Digest::SHA1
13
20
 
14
21
  # Specify a different digest class. The default is +Digest::SHA1+. You
15
22
  # could try +Digest::SHA256+ by doing this:
@@ -30,7 +37,7 @@ module Gibbler
30
37
  # Objects that are a kind of Hash or Array are processed
31
38
  # recursively. The length of the returned String depends
32
39
  # on the digest type.
33
- def gibble
40
+ def gibbler
34
41
  #if h.respond_to? :__custom_gibbler
35
42
  # d = h.__custom_gibbler
36
43
  # a = __gibbler '%s:%s:%s' % [klass, d.size, d]
@@ -38,7 +45,8 @@ module Gibbler
38
45
  # a
39
46
  #end
40
47
  gibbler_debug :GIBBLER, self.class, self
41
- @__gibble__ = self.__gibbler
48
+ @__gibbler_digest__ = Gibbler::Digest.new self.__gibbler
49
+ @__gibbler_digest__
42
50
  end
43
51
 
44
52
  # Sends +str+ to Digest::SHA1.hexdigest. If another digest class
@@ -50,12 +58,12 @@ module Gibbler
50
58
 
51
59
  # Has this object been modified?
52
60
  #
53
- # This method compares the return value from gibble with the
54
- # previous value returned by gibble (the value is stored in
55
- # <tt>@__gibble__</tt>)
61
+ # This method compares the return value from digest with the
62
+ # previous value returned by gibbler (the value is stored in
63
+ # <tt>@__gibbler_digest__</tt>)
56
64
  def gibbled?
57
- @__gibble__ ||= self.gibble
58
- was, now = @__gibble__.clone, self.gibble
65
+ @__gibbler_digest__ ||= self.gibbler
66
+ was, now = @__gibbler_digest__.clone, self.gibbler
59
67
  gibbler_debug :gibbled?, was, now
60
68
  was != now
61
69
  end
@@ -70,28 +78,34 @@ module Gibbler
70
78
  end
71
79
 
72
80
  # Gets the list of instance variables from the standard implementation
73
- # of the instance_variables method and removes <tt>@__gibble__</tt>.
81
+ # of the instance_variables method and removes all that
82
+ # begin with <tt>@__gibbler</tt>.
74
83
  # Any class the includes Gibbler or Gibbler::* will use this version of
75
84
  # instance_variables. It's important because we don't want the current
76
- # gibble value to affect the next gibble.
85
+ # digest value to affect the next gibble.
86
+ #
87
+ # This is also critical for objects that include Gibbler::Complex b/c
88
+ # it deals explicitly with instance variables. If it sees the __gibbler
89
+ # variables it will go bananas.
90
+ #
77
91
  def instance_variables
78
92
  vars = super
79
- vars.reject! { |x| x.to_s == '@__gibble__' }
93
+ vars.reject! { |e| e.to_s =~ /^@__gibble/ }
80
94
  vars
81
95
  end
82
96
 
83
97
  module Complex
84
98
  include Gibbler
85
- # Creates a gibble based on:
99
+ # Creates a digest based on:
86
100
  # * An Array of instance variable names and values in the format: <tt>CLASS:LENGTH:VALUE</tt>
87
- # * The gibble method is called on each element so if it is a Hash or Array etc it
101
+ # * The gibbler method is called on each element so if it is a Hash or Array etc it
88
102
  # will be parsed recursively according to the gibbler method for that class type.
89
- # * Gibble the Array of gibbles
103
+ # * Digest the Array of digests
90
104
  # * Return the digest for <tt>class:length:value</tt> where:
91
105
  # * "class" is equal to the current object class (e.g. FullHouse).
92
- # * "length" is the size of the Array of gibbles (which should equal
106
+ # * "length" is the size of the Array of digests (which should equal
93
107
  # the number of instance variables in the object).
94
- # * "value" is the Array of gibbles joined with a colon (":").
108
+ # * "value" is the Array of digests joined with a colon (":").
95
109
  #
96
110
  # This method can be used by any class which stores values in instance variables.
97
111
  #
@@ -107,11 +121,20 @@ module Gibbler
107
121
  gibbler_debug klass, a, [klass, d.size, d]
108
122
  a
109
123
  end
124
+
125
+ def __gibbler_revert
126
+ state = self.gibbler_object @__gibbler_digest__
127
+ state.instance_variables do |n|
128
+ v = state.instance_variable_get n
129
+ self.instance_variable_set v
130
+ end
131
+ end
132
+
110
133
  end
111
134
 
112
135
  module String
113
136
  include Gibbler
114
- # Creates a gibble based on: <tt>CLASS:LENGTH:VALUE</tt>.
137
+ # Creates a digest based on: <tt>CLASS:LENGTH:VALUE</tt>.
115
138
  # This method can be used for any class where the <tt>to_s</tt>
116
139
  # method returns an appropriate unique value for this instance.
117
140
  # It's used by default for Symbol, Class, Fixnum, and Bignum.
@@ -138,16 +161,16 @@ module Gibbler
138
161
  module Hash
139
162
  include Gibbler
140
163
 
141
- # Creates a gibble based on:
164
+ # Creates a digest based on:
142
165
  # * parse each key, value pair into an Array containing keys: <tt>CLASS:KEY:VALUE.__gibbler</tt>
143
- # * The gibble method is called on each element so if it is a Hash or Array etc it
166
+ # * The gibbler method is called on each element so if it is a Hash or Array etc it
144
167
  # will be parsed recursively according to the gibbler method for that class type.
145
- # * Gibble the Array of gibbles
168
+ # * Digest the Array of digests
146
169
  # * Return the digest for <tt>class:length:value</tt> where:
147
170
  # * "class" is equal to the current object class (e.g. Hash).
148
- # * "length" is the size of the Array of gibbles (which should equal
171
+ # * "length" is the size of the Array of digests (which should equal
149
172
  # the number of keys in the original Hash object).
150
- # * "value" is the Array of gibbles joined with a colon (":").
173
+ # * "value" is the Array of digests joined with a colon (":").
151
174
  #
152
175
  # This method can be used by any class with a <tt>keys</tt> method.
153
176
  #
@@ -174,16 +197,16 @@ module Gibbler
174
197
  module Array
175
198
  include Gibbler
176
199
 
177
- # Creates a gibble based on:
178
- # * parse each element into an Array of gibbles like: <tt>CLASS:INDEX:VALUE.__gibbler</tt>
179
- # * The gibble method is called on each element so if it is a Hash or Array etc it
200
+ # Creates a digest based on:
201
+ # * parse each element into an Array of digests like: <tt>CLASS:INDEX:VALUE.__gibbler</tt>
202
+ # * The gibbler method is called on each element so if it is a Hash or Array etc it
180
203
  # will be parsed recursively according to the gibbler method for that class type.
181
- # * Gibble the Array of gibbles
204
+ # * Digest the Array of digests
182
205
  # * Return the digest for <tt>class:length:value</tt> where:
183
206
  # * "class" is equal to the current object class (e.g. Array).
184
- # * "length" is the size of the Array of gibbles (which should equal
207
+ # * "length" is the size of the Array of digests (which should equal
185
208
  # the number of elements in the original Array object).
186
- # * "value" is the Array of gibbles joined with a colon (":").
209
+ # * "value" is the Array of digests joined with a colon (":").
187
210
  #
188
211
  # This method can be used by any class with an <tt>each</tt> method.
189
212
  #
@@ -228,8 +251,8 @@ module Gibbler
228
251
  ##
229
252
  ##end
230
253
  ##++
231
-
232
-
254
+
255
+
233
256
  end
234
257
 
235
258
  class Hash
@@ -0,0 +1,27 @@
1
+
2
+ # = Gibbler::Digest
3
+ #
4
+ # A tiny subclass of String which adds a
5
+ # few digest related convenience methods.
6
+ #
7
+ class Gibbler::Digest < String
8
+
9
+ # Returns the first 8 characters of itself (the digest).
10
+ #
11
+ # e.g.
12
+ #
13
+ # "kimmy".gibbler # => c8027100ecc54945ab15ddac529230e38b1ba6a1
14
+ # "kimmy".gibbler.short # => c8027100
15
+ #
16
+ def short
17
+ self[0..7]
18
+ end
19
+
20
+
21
+ def ==(g)
22
+ return true if self.to_s == g.to_s
23
+ return true if self.short.to_s == g.to_s
24
+ false
25
+ end
26
+
27
+ end
@@ -0,0 +1,170 @@
1
+
2
+
3
+
4
+ module Gibbler
5
+ class NoRevert < Gibbler::Error
6
+ def message; "Revert not implemented for #{@obj}" end
7
+ end
8
+ class NoHistory < Gibbler::Error
9
+ def message; "No history for #{@obj}"; end
10
+ end
11
+ class BadDigest < Gibbler::Error
12
+ def message; "Unknown digest: #{@obj}"; end
13
+ end
14
+
15
+ module History
16
+
17
+ @@mutex = Mutex.new
18
+
19
+ def self.mutex; @@mutex; end
20
+
21
+ # Returns an Array of digests in the order they were committed.
22
+ # If +short+ is anything but false, the digests will be converted
23
+ # to the short 8 character digests.
24
+ def gibbler_history(short=false)
25
+ # Only a single thread should attempt to initialize the store.
26
+ if @__gibbler_history__.nil?
27
+ @@mutex.synchronize {
28
+ @__gibbler_history__ ||= { :history => [], :objects => {}, :stamp => {} }
29
+ }
30
+ end
31
+ if short == false
32
+ @__gibbler_history__[:history]
33
+ else
34
+ @__gibbler_history__[:history].collect { |g| g.short }
35
+ end
36
+ end
37
+
38
+ # Returns the object stored under the given digest +g+.
39
+ # If +g+ is not a valid digest, returns nil.
40
+ def gibbler_object(g=nil)
41
+ g = gibbler_find_long g
42
+ g = self.gibbler_history.last if g.nil?
43
+
44
+ return unless gibbler_valid? g
45
+ @__gibbler_history__[:objects][ g ]
46
+ end
47
+
48
+ # Returns the timestamp (a Time object) when the digest +g+ was committed.
49
+ # If +g+ is not a valid gibble, returns nil.
50
+ def gibbler_stamp(g=nil)
51
+ g = gibbler_find_long g
52
+ g = self.gibbler_history.last if g.nil?
53
+ return unless gibbler_valid? g
54
+ @__gibbler_history__[:stamp][ g ]
55
+ end
56
+
57
+ # Stores a clone of the current object instance using the current
58
+ # digest value. If the object was not changed, this method does
59
+ # nothing but return the gibble.
60
+ #
61
+ # NOTE: This method is not fully thread safe. It uses a Mutex.synchronize
62
+ # but there's a race condition where two threads can attempt to commit at
63
+ # near the same time. The first will get the lock and create the commit.
64
+ # The second will get the lock and create another commit immediately
65
+ # after. What we probably want is for the second thread to return the
66
+ # digest for that first snapshot, but how do we know this was a result
67
+ # of the race conditioon rather than two legitimate calls for a snapshot?
68
+ def gibbler_commit
69
+ now, digest, point = nil,nil,nil
70
+
71
+ if @__gibbler_history__.nil?
72
+ @@mutex.synchronize {
73
+ @__gibbler_history__ ||= { :history => [], :objects => {}, :stamp => {} }
74
+ }
75
+ end
76
+
77
+ @@mutex.synchronize {
78
+ now, digest, point = Time.now, self.gibbler, self.clone
79
+ @__gibbler_history__[:history] << digest
80
+ @__gibbler_history__[:stamp][digest] = now
81
+ @__gibbler_history__[:objects][digest] = point
82
+ }
83
+
84
+ digest
85
+ end
86
+
87
+ # Revert this object to a previously known state. If called without arguments
88
+ # it will revert to the most recent commit. If a digest is specified +g+, it
89
+ # will revert to that point.
90
+ #
91
+ # Ruby does not support replacing self (<tt>self = previous_self</tt>) so each
92
+ # object type needs to implement its own __gibbler_revert method. This default
93
+ # run some common checks and then defers to self.__gibbler_revert.
94
+ #
95
+ # Raise the following exceptions:
96
+ # * NoRevert: if this object doesn't have a __gibbler_revert method
97
+ # * NoHistory: This object has no commits
98
+ # * BadDigest: The given digest is not in the history for this object
99
+ #
100
+ # If +g+ matches the current digest value this method does nothing.
101
+ #
102
+ # Returns the new digest (+g+).
103
+ def gibbler_revert(g=nil)
104
+ raise NoRevert unless self.respond_to?(:__gibbler_revert)
105
+ raise NoHistory, self.class unless gibbler_history?
106
+ raise BadDigest, g if !g.nil? && !gibbler_valid?(g)
107
+
108
+ g = self.gibbler_history.last if g.nil?
109
+ g = gibbler_find_long g
110
+
111
+ # Do nothing if the given digest matches the current gibble.
112
+ # NOTE: We use __gibbler b/c it doesn't update @@__gibbler_digest__.
113
+ unless self.__gibbler == g
114
+ @@mutex.synchronize {
115
+ # Always make sure @__gibbler_digest__ is a Gibbler::Digest
116
+ @__gibbler_digest__ = g.is_a?(Gibbler::Digest) ? g : Gibbler::Digest.new(g)
117
+ self.__gibbler_revert
118
+ }
119
+ end
120
+
121
+ @__gibbler_digest__
122
+ end
123
+
124
+ # Is the given digest +g+ contained in the history for this object?
125
+ def gibbler_valid?(g)
126
+ return false unless gibbler_history?
127
+ gibbler_history.member? gibbler_find_long(g)
128
+ end
129
+
130
+ # Does the current object have any history?
131
+ def gibbler_history?
132
+ !gibbler_history.empty?
133
+ end
134
+
135
+ # Returns the long digest associated to the short digest +g+.
136
+ # If g is longer than 8 characters it returns the value of +g+.
137
+ def gibbler_find_long(g)
138
+ return if g.nil?
139
+ return g if g.size > 8
140
+ gibbler_history.select { |d| d.match /\A#{g}/ }.first
141
+ end
142
+ end
143
+
144
+ end
145
+
146
+ class Hash
147
+ include Gibbler::History
148
+ def __gibbler_revert
149
+ self.clear
150
+ self.merge! self.gibbler_object @__gibbler_digest__
151
+ end
152
+ end
153
+
154
+ class Array
155
+ include Gibbler::History
156
+ def __gibbler_revert
157
+ self.clear
158
+ self.push *(self.gibbler_object @__gibbler_digest__)
159
+ end
160
+ end
161
+
162
+ class String
163
+ include Gibbler::History
164
+ def __gibbler_revert
165
+ self.clear
166
+ self << (self.gibbler_object @__gibbler_digest__)
167
+ end
168
+ end
169
+
170
+
@@ -0,0 +1,4 @@
1
+
2
+
3
+ require 'gibbler/mixins/string'
4
+
@@ -0,0 +1,9 @@
1
+
2
+
3
+ class String
4
+ unless method_defined? :clear
5
+ def clear
6
+ replace ""
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+
2
+ library :gibbler, 'lib'
3
+
4
+ group "Mixins"
5
+
6
+ tryouts "String" do
7
+
8
+ drill "has String#clear" do
9
+ "".respond_to? :clear
10
+ end
11
+
12
+ end
13
+
@@ -0,0 +1,26 @@
1
+
2
+ library :gibbler, 'lib'
3
+
4
+ group "Gibbler::Digest"
5
+
6
+ tryouts "All methods" do
7
+
8
+
9
+ dream :class, Gibbler::Digest
10
+ dream 'c8027100'
11
+ drill "has short method" do
12
+ "kimmy".gibbler.short
13
+ end
14
+
15
+ dream :class, Gibbler::Digest
16
+ dream "12345678"
17
+ drill "can Gibbler::Digest#short" do
18
+ Gibbler::Digest.new("1234567890").short
19
+ end
20
+
21
+ drill "can return true if compared with short", true do
22
+ Gibbler::Digest.new("1234567890") == "12345678"
23
+ end
24
+
25
+ end
26
+
@@ -0,0 +1,92 @@
1
+
2
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
3
+ group "Gibbler Gazette"
4
+
5
+ Gibbler.enable_debug if Tryouts.verbose > 3
6
+
7
+ tryouts "Basic syntax with SHA1" do
8
+
9
+ dream :respond_to?, :gibbler
10
+ dream :gibbler, '52be7494a602d85ff5d8a8ab4ffe7f1b171587df'
11
+ drill "Symbol can gibbler", :kimmy
12
+
13
+ dream :respond_to?, :gibbler
14
+ dream :gibbler, 'c8027100ecc54945ab15ddac529230e38b1ba6a1'
15
+ drill "String can gibbler" do
16
+ "kimmy"
17
+ end
18
+
19
+ drill "String and Symbol return different digests", true do
20
+ :kimmy.gibbler != "kimmy"
21
+ end
22
+
23
+ dream :respond_to?, :gibbler
24
+ dream :gibbler, '25ac269ae3ef18cdb4143ad02ca315afb5026de9'
25
+ drill "Class can gibbler", Class
26
+
27
+ dream :respond_to?, :gibbler
28
+ dream :gibbler, '4fdcadc66a38feb9c57faf3c5a18d5e76a6d29bf'
29
+ drill "Empty Hash instance", Hash.new
30
+
31
+ dream :gibbler, 'a9cad665549bd22a4346fcf602d9d3c3b0482bbe'
32
+ drill "Fixnum instance" do
33
+ 1
34
+ end
35
+
36
+ dream :gibbler, '259afadb4ef8abaeb367db97d0c3015c8a4a504a'
37
+ drill "Bignum instance" do
38
+ 100000000000
39
+ end
40
+
41
+ dream :gibbler, "1d4b62e1e9f2c097b0cefb6877bf47c2015cdd21"
42
+ drill "Populated Hash instance" do
43
+ {
44
+ :a => [1,2,3, [4,5,6]],
45
+ :b => { :c => Class }
46
+ }
47
+ end
48
+
49
+ dream :respond_to?, :gibbler
50
+ dream :gibbler, '48fda57c05684c9e5c3259557851943572183a21'
51
+ drill "Empty Array instance", Array.new
52
+
53
+ dream :gibbler, "884e5713aa70468333459f80aea1bb05394ca4ba"
54
+ drill "Populated Array instance" do
55
+ [1, 22222222222, :runtime, [2, "three", [Object, true]]]
56
+ end
57
+
58
+ drill "Knows when a Hash has not changed", false do
59
+ a = { :magic => true }
60
+ a.gibbler
61
+ a[:magic] = true
62
+ a.gibbled?
63
+ end
64
+
65
+ drill "Knows when a Hash has changed", true do
66
+ a = { :magic => true }
67
+ a.gibbler
68
+ a[:magic] = false
69
+ a.gibbled?
70
+ end
71
+
72
+ dream :gibbler, "6ea546919dc4caa2bab69799b71d48810a1b48fa"
73
+ drill "works on arbitrary objects" do
74
+ class ::FullHouse
75
+ include Gibbler::Complex
76
+ attr_accessor :roles
77
+ end
78
+ a = FullHouse.new
79
+ a.roles = [:jesse, :joey, :danny, :kimmy, :michelle, :dj, :stephanie]
80
+ a
81
+ end
82
+
83
+ drill "doesn't reveal @__gibbler_digest__ instance variable", false do
84
+ a = {}
85
+ a.gibbler # We need to gibbler first so it sets a value to the instance var
86
+ val = Tryouts.sysinfo.ruby[1] == 9 ? :'@__gibbler_digest__' : '@__gibbler_digest__'
87
+ a.instance_variables.member? val
88
+ end
89
+
90
+
91
+ end
92
+
@@ -0,0 +1,40 @@
1
+
2
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
3
+ group "Gibbler Gazette"
4
+
5
+ Gibbler.enable_debug if Tryouts.verbose > 3
6
+
7
+
8
+ tryouts "Basic syntax with SHA256" do
9
+
10
+ # NOTE: JRuby require that we use OpenSSL::Digest::SHA256
11
+ if Tryouts.sysinfo.vm == :java
12
+ drill "Can change Digest type", OpenSSL::Digest::SHA256 do
13
+ Gibbler.digest_type = OpenSSL::Digest::SHA256
14
+ end
15
+ else
16
+ drill "Can change Digest type", Digest::SHA256 do
17
+ Gibbler.digest_type = Digest::SHA256
18
+ end
19
+ end
20
+
21
+ dream :respond_to?, :gibbler
22
+ dream :gibbler, '754f87ca720ec256633a286d9270d68478850b2abd7b0ae65021cb769ae70c08'
23
+ drill "A Symbol can gibbler", :anything
24
+
25
+ dream :respond_to?, :gibbler
26
+ dream :gibbler, 'd345c0afb4e8da0133a3946d3bd9b2622b0acdd8d6cc1237470cc637a9e4777f'
27
+ drill "Class can gibbler", Class
28
+
29
+ dream :respond_to?, :gibbler
30
+ dream :gibbler, '88d2bcbd68ce593fd2e0e06f276f7301357516291b95c0c53038e61a9bf091e5'
31
+ drill "Empty Hash instance", {}
32
+
33
+ drill "Can return Digest type", Digest::SHA1 do
34
+ Gibbler.digest_type = Digest::SHA1
35
+ end
36
+
37
+ end
38
+
39
+
40
+
@@ -0,0 +1,42 @@
1
+
2
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
3
+ library 'gibbler/history', File.dirname(__FILE__), '..', 'lib'
4
+
5
+ group "History"
6
+
7
+ tryouts "Basics" do
8
+
9
+ dream "d7049916ddb25e6cc438b1028fb957e5139f9910"
10
+ drill "can convert short digest into long" do
11
+ a = { :magic => :original }
12
+ g = a.gibbler_commit.short
13
+ stash :short, g
14
+ a.gibbler_find_long g
15
+ end
16
+
17
+ dream :class, Time
18
+ drill "can return most recent stamp" do
19
+ a = { :magic => :original }
20
+ a.gibbler_commit
21
+ stash :hist, a.gibbler_history
22
+ a.gibbler_stamp
23
+ end
24
+
25
+ dream ["d7049916ddb25e6cc438b1028fb957e5139f9910", "0b11c377fccd44554a601e5d2b135c46dc1c4cb1"]
26
+ drill "can return history" do
27
+ a = { :magic => :original }
28
+ a.gibbler_commit
29
+ a[:magic] = :changed
30
+ a.gibbler_commit
31
+ a.gibbler_history
32
+ end
33
+
34
+ dream ["d7049916", "0b11c377"]
35
+ drill "can return history (short)" do
36
+ a = { :magic => :original }
37
+ a.gibbler_commit
38
+ a[:magic] = :changed
39
+ a.gibbler_commit
40
+ a.gibbler_history(:short)
41
+ end
42
+ end
@@ -0,0 +1,91 @@
1
+
2
+
3
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
4
+ library 'gibbler/history', File.dirname(__FILE__), '..', 'lib'
5
+
6
+ group "History"
7
+
8
+ Gibbler.enable_debug if Tryouts.verbose > 3
9
+
10
+
11
+ tryouts "Hash History" do
12
+
13
+ drill "Setup Hash class", Hash do
14
+ class ::Hash
15
+ include Gibbler::History
16
+ end
17
+ end
18
+
19
+ drill "doesn't reveal @__gibbler_history__ instance variable", false do
20
+ a = {}
21
+ a.gibbler # We need to gibbler first so it sets a value to the instance var
22
+ val = Tryouts.sysinfo.ruby[1] == 9 ? :'@__gibbler_history__' : '@__gibbler_history__'
23
+ a.instance_variables.member? val
24
+ end
25
+
26
+ drill "can take a Hash snapshot", 'd7049916ddb25e6cc438b1028fb957e5139f9910' do
27
+ a = { :magic => :original }
28
+ a.gibbler_commit
29
+ end
30
+
31
+ dream :class, Array
32
+ dream :size, 2
33
+ dream ['d7049916ddb25e6cc438b1028fb957e5139f9910', 'b668098e16d08898532bf3aa33ce2253a3a4150e']
34
+ drill "return a Hash history" do
35
+ a = { :magic => :original }
36
+ a.gibbler_commit
37
+ a[:magic] = :updated
38
+ a.gibbler_commit
39
+ a.gibbler_history
40
+ end
41
+
42
+ dream 'd7049916ddb25e6cc438b1028fb957e5139f9910'
43
+ drill "can revert Hash" do
44
+ a = { :magic => :original }
45
+ a.gibbler_commit
46
+ a[:magic] = :updated
47
+ a.gibbler_revert
48
+ end
49
+
50
+ drill "knows a valid gibble", true do
51
+ a = { :magic => :original }
52
+ a.gibbler_commit
53
+ a.gibbler_valid? 'd7049916ddb25e6cc438b1028fb957e5139f9910'
54
+ end
55
+
56
+ drill "knows an invalid gibble", false do
57
+ a = { :magic => :original }
58
+ a.gibbler_commit
59
+ a.gibbler_valid? '2222222222222222222222222222222222222222'
60
+ end
61
+
62
+ dream Hash[:magic => :original]
63
+ drill "can revert to any valid gibble" do
64
+ a = { :magic => :original }
65
+ a.gibbler_commit
66
+ a[:magic] = :updated
67
+ a.gibbler_commit
68
+ a[:magic] = :changed
69
+ a.gibbler_commit
70
+ a.gibbler_revert 'd7049916ddb25e6cc438b1028fb957e5139f9910'
71
+ a
72
+ end
73
+
74
+ dream Hash[:magic => :original]
75
+ dream :gibbler, 'd7049916ddb25e6cc438b1028fb957e5139f9910'
76
+ drill "revert does nothing if digest is the same as current one" do
77
+ a = { :magic => :original }
78
+ a.gibbler_commit
79
+ a.gibbler_revert
80
+ a
81
+ end
82
+
83
+ dream 'd7049916ddb25e6cc438b1028fb957e5139f9910'
84
+ drill "can revert using short gibble" do
85
+ a = { :magic => :original }
86
+ a.gibbler_commit
87
+ a[:magic] = :updated
88
+ a.gibbler_revert 'd7049916'
89
+ end
90
+
91
+ end
@@ -0,0 +1,44 @@
1
+
2
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
3
+ library 'gibbler/history', File.dirname(__FILE__), '..', 'lib'
4
+
5
+ group "History"
6
+
7
+ Gibbler.enable_debug if Tryouts.verbose > 3
8
+
9
+
10
+ tryouts "Array History" do
11
+
12
+ drill "Setup Array class", Array do
13
+ class ::Array
14
+ include Gibbler::History
15
+ end
16
+ end
17
+
18
+ drill "can take a Array snapshot", 'd95fcabb498ae282f356eba63da541e4f72c6efa' do
19
+ a = [:jesse]
20
+ a.gibbler_commit
21
+ end
22
+
23
+ dream :class, Array
24
+ dream :size, 2
25
+ dream ['d95fcabb498ae282f356eba63da541e4f72c6efa', 'eebcb2e84e828b1a7207af4d588cf41fd4c6393a']
26
+ drill "return an Array history" do
27
+ a = [:jesse]
28
+ a.gibbler_commit
29
+ a << :joey
30
+ a.gibbler_commit
31
+ a.gibbler_history
32
+ end
33
+
34
+ dream 'd95fcabb498ae282f356eba63da541e4f72c6efa'
35
+ drill "can revert Array" do
36
+ a = [:jesse]
37
+ stash :original, a.gibbler_commit
38
+ a << :joey
39
+ stash :updated, a.gibbler
40
+ a.gibbler_revert
41
+ end
42
+
43
+
44
+ end
@@ -0,0 +1,43 @@
1
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
2
+ library 'gibbler/history', File.dirname(__FILE__), '..', 'lib'
3
+
4
+ group "History"
5
+
6
+ Gibbler.enable_debug if Tryouts.verbose > 3
7
+
8
+
9
+ tryouts "String History" do
10
+
11
+ drill "Setup String class", String do
12
+ class ::String
13
+ include Gibbler::History
14
+ end
15
+ end
16
+
17
+ drill "can take a String snapshot", 'c8027100ecc54945ab15ddac529230e38b1ba6a1' do
18
+ a = "kimmy"
19
+ a.gibbler_commit
20
+ end
21
+
22
+ dream :class, Array
23
+ dream :size, 2
24
+ dream ['c8027100ecc54945ab15ddac529230e38b1ba6a1', '692c05d3186baf2da36e87b7bc5fe53ef13b902e']
25
+ drill "return a String history" do
26
+ a = "kimmy"
27
+ a.gibbler_commit
28
+ a << " gibbler"
29
+ a.gibbler_commit
30
+ a.gibbler_history
31
+ end
32
+
33
+ dream 'c8027100ecc54945ab15ddac529230e38b1ba6a1'
34
+ drill "can revert String" do
35
+ a = "kimmy"
36
+ stash :original, a.gibbler_commit
37
+ a << " gibbler"
38
+ stash :updated, a.gibbler
39
+ a.gibbler_revert
40
+ end
41
+
42
+ end
43
+
@@ -0,0 +1,45 @@
1
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
2
+ library 'gibbler/history', File.dirname(__FILE__), '..', 'lib'
3
+
4
+ group "History"
5
+
6
+ Gibbler.enable_debug if Tryouts.verbose > 3
7
+
8
+
9
+
10
+ tryouts "Arbitrary Object History" do
11
+
12
+ drill "Setup String class", 'FullHouse' do
13
+ class ::FullHouse
14
+ include Gibbler::Complex
15
+ include Gibbler::History
16
+ attr_accessor :roles
17
+ end
18
+ FullHouse.to_s
19
+ end
20
+
21
+ drill "can take a FullHouse snapshot", '4192d4cb59975813f117a51dcd4454ac16df6703' do
22
+ a = FullHouse.new
23
+ a.gibbler_commit
24
+ end
25
+
26
+ dream ['4192d4cb59975813f117a51dcd4454ac16df6703', '2c6957aa1e734d2a3a71caf569a7461a3bf26f11']
27
+ drill "return a FullHouse history" do
28
+ a = FullHouse.new
29
+ a.gibbler_commit
30
+ a.roles = [:jesse]
31
+ a.gibbler_commit
32
+ a.gibbler_history
33
+ end
34
+
35
+ dream '4192d4cb59975813f117a51dcd4454ac16df6703'
36
+ drill "can revert FullHouse" do
37
+ a = FullHouse.new
38
+ stash :original, a.gibbler_commit
39
+ a.roles = [:jesse]
40
+ stash :updated, a.gibbler
41
+ a.gibbler_revert
42
+ end
43
+
44
+ end
45
+
@@ -0,0 +1,30 @@
1
+
2
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
3
+ library 'gibbler/history', File.dirname(__FILE__), '..', 'lib'
4
+
5
+ group "History"
6
+
7
+ Gibbler.enable_debug if Tryouts.verbose > 3
8
+
9
+
10
+ tryouts "Exceptions" do
11
+
12
+ dream :exception, Gibbler::BadDigest
13
+ drill "raises exception when reverting to unknown gibble" do
14
+ a = {}
15
+ a.gibbler_commit
16
+ a.gibbler_revert '2222222222222222222222222222222222222222'
17
+ end
18
+
19
+ dream :exception, Gibbler::NoHistory
20
+ drill "raises exception when reverting and there's no history" do
21
+ a = []
22
+ a.gibbler_revert
23
+ end
24
+
25
+ dream :exception, NoMethodError
26
+ drill "raises exception when reverting an unsupported object" do
27
+ :kimmy.gibbler_revert
28
+ end
29
+
30
+ end
@@ -0,0 +1,80 @@
1
+
2
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
3
+ group "Performance"
4
+
5
+ tryouts "Speed", :benchmark do
6
+ # NOTE: gibbler is slower when history is enabled.
7
+ drill "Setup variables" do
8
+ @@array = (1..10000).map { rand }
9
+ values = (1..10000).map { rand }
10
+ zipped = @@array.zip(values)
11
+ @@hash = Hash[*zipped]
12
+ end
13
+
14
+ drill "Array#hash", 5 do
15
+ @@array.hash
16
+ end
17
+
18
+
19
+ dream :mean, 0.21
20
+ drill "Array#gibbler", 5 do
21
+ @@array.gibbler
22
+ end
23
+
24
+ drill "Hash#hash", 5 do
25
+ @@hash.hash
26
+ end
27
+
28
+ dream :mean, 1.1
29
+ drill "Hash#gibbler", 5 do
30
+ @@hash.gibbler
31
+ end
32
+
33
+ end
34
+
35
+ repetitions = 100 # at 100_000 hash shows errors
36
+ sample_size = 1..100
37
+
38
+ tryouts "Uniqueness", :api do
39
+
40
+ drill "Array#hash, all unique", 0 do
41
+ seen = []
42
+ repetitions.times do
43
+ srand
44
+ seen << ((sample_size).map { rand }).hash
45
+ end
46
+ seen.size - seen.uniq.size
47
+ end
48
+
49
+ drill "Hash#hash, all unique", 0 do
50
+ seen = []
51
+ repetitions.times do
52
+ srand
53
+ seen << Hash[*(sample_size).map { rand }.zip((sample_size).map { rand })].hash
54
+ end
55
+ seen.size - seen.uniq.size
56
+ end
57
+
58
+ drill "Array#gibbler, all unique", 0 do
59
+ seen = []
60
+ repetitions.times do
61
+ srand
62
+ seen << ((sample_size).map { rand }).gibbler
63
+ end
64
+ seen.size - seen.uniq.size
65
+ end
66
+
67
+ drill "Hash#gibbler, all unique", 0 do
68
+ seen = []
69
+ repetitions.times do
70
+ srand
71
+ seen << Hash[*(sample_size).map { rand }.zip((sample_size).map { rand })].gibbler
72
+ end
73
+ seen.size - seen.uniq.size
74
+ end
75
+
76
+
77
+
78
+
79
+
80
+ end
@@ -0,0 +1,30 @@
1
+
2
+ library :gibbler, File.dirname(__FILE__), '..', 'lib'
3
+ group "Object#hash"
4
+
5
+ tryout "Object#hash (Ruby 1.9 only)", :api do
6
+
7
+ drill "Object", Object.hash, 1892070
8
+ drill "Class", Class.hash, 1892030
9
+ drill "Array", Array.hash, 1866770
10
+ drill "Hash", Hash.hash, 1863840
11
+
12
+ end
13
+
14
+ tryouts "Object#hash (Ruby 1.8 only)", :api do
15
+
16
+ drill "Object", Object.hash, 118420
17
+ drill "Class", Class.hash, 118400
18
+ drill "Array", Array.hash, 104100
19
+ drill "Hash", Hash.hash, 102590
20
+
21
+ end
22
+
23
+ tryouts "Object#hash (JRuby only)", :api do
24
+
25
+ drill "Object", Object.hash, 1
26
+ drill "Class", Class.hash, 3
27
+ drill "Array", Array.hash, 46
28
+ drill "Hash", Hash.hash, 43
29
+
30
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gibbler
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.4"
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-06-30 00:00:00 -04:00
12
+ date: 2009-07-01 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -23,12 +23,28 @@ extra_rdoc_files:
23
23
  - README.rdoc
24
24
  - LICENSE.txt
25
25
  files:
26
- - README.rdoc
27
26
  - CHANGES.txt
28
27
  - LICENSE.txt
28
+ - README.rdoc
29
29
  - Rakefile
30
30
  - gibbler.gemspec
31
31
  - lib/gibbler.rb
32
+ - lib/gibbler/digest.rb
33
+ - lib/gibbler/history.rb
34
+ - lib/gibbler/mixins.rb
35
+ - lib/gibbler/mixins/string.rb
36
+ - tryouts/01_mixins_tryouts.rb
37
+ - tryouts/05_gibbler_digest_tryouts.rb
38
+ - tryouts/10_basic_tryouts.rb
39
+ - tryouts/11_basic_sha256_tryouts.rb
40
+ - tryouts/50_history_tryouts.rb
41
+ - tryouts/51_hash_history_tryouts.rb
42
+ - tryouts/52_array_history_tryouts.rb
43
+ - tryouts/53_string_history_tryouts.rb
44
+ - tryouts/57_arbitrary_history_tryouts.rb
45
+ - tryouts/59_history_exceptions_tryouts.rb
46
+ - tryouts/80_performance_tryouts.rb
47
+ - tryouts/object_hash_demo.rb
32
48
  has_rdoc: true
33
49
  homepage: http://github.com/delano/gibbler
34
50
  licenses: []