gibbler 0.8.10 → 0.10.0.pre.RC1

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/README.md ADDED
@@ -0,0 +1,250 @@
1
+ # Gibbler - v0.10.0
2
+
3
+ Git-like hashes and history for Ruby objects for Ruby 3.1+.
4
+
5
+ Check out [this post on RubyInside](http://www.rubyinside.com/gibbler-git-like-hashes-and-history-for-ruby-objects-1980.html).
6
+
7
+ * [Repo](https://github.com/delano/gibbler)
8
+ * [Docs](https://delanotes.com/gibbler)
9
+ * [Sponsor](https://solutious.com/)
10
+ * [Inspiration](https://www.youtube.com/watch?v=fipD4DdV48g)
11
+
12
+ ## Installation
13
+
14
+ Install the gem and add to the application's Gemfile by executing:
15
+
16
+ $ bundle add gibbler
17
+
18
+ If bundler is not being used to manage dependencies, install the gem by executing:
19
+
20
+ $ gem install gibbler
21
+
22
+ ## Usage
23
+
24
+ ### Example 1 -- Standalone Usage
25
+
26
+ ```ruby
27
+ require 'gibbler'
28
+
29
+ g = Gibbler.new 'id', 1001 # => f4fb3796ababa3788d1bded8fdc589ab1ccb1c3d
30
+ g.base(36) # => sm71s7eam4hm5jlsuzlqkbuktwpe5h9
31
+
32
+ g == 'f4fb3796ababa3788d1bded8fdc589ab1ccb1c3d' # => true
33
+ g === 'f4fb379' # => true
34
+ ```
35
+
36
+ ### Example 2 -- Mixins Usage
37
+
38
+ ```ruby
39
+ require 'gibbler/mixins'
40
+
41
+ "kimmy".gibbler # => c8027100ecc54945ab15ddac529230e38b1ba6a1
42
+ :kimmy.gibbler # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df
43
+
44
+ config = {}
45
+ config.gibbler # => 4fdcadc66a38feb9c57faf3c5a18d5e76a6d29bf
46
+ config.gibbled? # => false
47
+
48
+ config[:server] = {
49
+ :users => [:dave, :ali],
50
+ :ports => [22, 80, 443]
51
+ }
52
+ config.gibbled? # => true
53
+ config.gibbler # => ef23d605f8c4fc80a8e580f9a0e8dab8426454a8
54
+
55
+ config[:server][:users] << :yanni
56
+
57
+ config.gibbler # => 4c558a56bc2abf5f8a845a69e47ceb5e0003683f
58
+
59
+ config.gibbler.short # => 4c558a56
60
+
61
+ config.gibbler.base36 # => 8x00l83jov4j80i9vfzpaxr9jag23wf
62
+
63
+ config.gibbler.base36.short # => 8x00l83j
64
+ ```
65
+
66
+ ### Example 3 -- Object History
67
+
68
+ 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 `gibbler_commit` method creates a clone of the current object and stores in an instance variable using the current hash digest as the key.
69
+
70
+ ```ruby
71
+ require 'gibbler/mixins'
72
+ require 'gibbler/history'
73
+
74
+ a = { :magic => :original }
75
+ a.gibbler_commit # => d7049916ddb25e6cc438b1028fb957e5139f9910
76
+
77
+ a[:magic] = :updated
78
+ a.gibbler_commit # => b668098e16d08898532bf3aa33ce2253a3a4150e
79
+
80
+ a[:magic] = :changed
81
+ a.gibbler_commit # => 0b11c377fccd44554a601e5d2b135c46dc1c4cb1
82
+
83
+ a.gibbler_history # => d7049916, b668098e, 0b11c377
84
+
85
+ a.gibbler_revert! 'd7049916' # Return to a specific commit
86
+ a.gibbler # => d7049916ddb25e6cc438b1028fb957e5139f9910
87
+ a # => { :magic => :original }
88
+
89
+ a.delete :magic
90
+
91
+ a.gibbler_revert! # Return to the previous commit
92
+ a.gibbler # => 0b11c377fccd44554a601e5d2b135c46dc1c4cb1
93
+ a # => { :magic => :changed }
94
+
95
+
96
+ a.gibbler_object 'b668098e' # => { :magic => :updated }
97
+ a.gibbler_stamp # => 2009-07-01 18:56:52 -0400
98
+ ```
99
+
100
+ ![](https://delanotes.com/gibbler/img/whoababy.gif)
101
+
102
+
103
+ ### Example 4 -- Method Aliases
104
+
105
+ If you have control over the namespaces of your objects, you can use the method aliases to tighten up your code a bit. The "gibbler" and "gibbled?" methods can be accessed via "digest" and "changed?", respectively. (The reason they're not enabled by default is to avoid conflicts.)
106
+
107
+ ```ruby
108
+ require 'gibbler/aliases'
109
+
110
+ "kimmy".digest # => c8027100ecc54945ab15ddac529230e38b1ba6a1
111
+ :kimmy.digest # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df
112
+
113
+ a = [:a, :b, :c]
114
+ a.digest # => e554061823b8f06367555d1ee4c25b4ffee61944
115
+ a << :d
116
+ a.changed? # => true
117
+
118
+ The history methods also have aliases which remove the "gibbler_" prefix.
119
+
120
+ require 'gibbler/aliases'
121
+ require 'gibbler/history'
122
+
123
+ a = { :magic => :original }
124
+ a.commit
125
+ a.history
126
+ a.revert!
127
+ # etc...
128
+ ```
129
+
130
+ ### Example 5 -- Different Digest types
131
+
132
+ By default Gibbler creates SHA1 hashes. You can change this globally or per instance.
133
+
134
+ ```ruby
135
+ require 'gibbler/mixins'
136
+
137
+ Gibbler.digest_type = Digest::MD5
138
+
139
+ :kimmy.gibbler # => 0c61ff17f46223f355759934154d5dcb
140
+
141
+ :kimmy.gibbler(Digest::SHA1) # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df
142
+ ```
143
+
144
+ In Jruby, you can grab the digest types from the openssl library.
145
+
146
+ ```ruby
147
+ require 'openssl'
148
+
149
+ Gibbler.digest_type = OpenSSL::Digest::SHA256
150
+
151
+ :kimmy.gibbler # => 1069428e6273cf329436c3dce9b680d4d4e229d7b7...
152
+ ```
153
+
154
+ ### Example 6 -- All your base
155
+
156
+ ```ruby
157
+ require 'gibbler/mixins'
158
+
159
+ :kimmy.gibbler # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df
160
+ :kimmy.gibbler.base(16) # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df
161
+ :kimmy.gibbler.base(36) # => 9nydr6mpv6w4k8ngo3jtx0jz1n97h7j
162
+
163
+ :kimmy.gibbler.base(10) # => 472384540402900668368761869477227308873774630879
164
+ :kimmy.gibbler.to_i # => 472384540402900668368761869477227308873774630879
165
+ ```
166
+
167
+ ### Example 7 -- Global secret
168
+
169
+ Gibbler can prepend all digest inputs with a global secret. You can set this once per project to ensure your project's digests are unique.
170
+
171
+ ```ruby
172
+ require 'gibbler/mixins'
173
+
174
+ :kimmy.gibbler # => 52be7494a602d85ff5d8a8ab4ffe7f1b171587df
175
+
176
+ Gibbler.secret = "sUp0r5ekRu7"
177
+
178
+ :kimmy.gibbler # => 6c5f5aff4d809cec7e7da091214a35a2698489f8
179
+ ```
180
+
181
+ ### Supported Classes
182
+
183
+ Gibbler methods are available only to the classes which explicitly include them [see docs'(https://delanotes.com/gibbler) for details on which classes are supported by default). You can also extend custom objects:
184
+
185
+ ```ruby
186
+ class FullHouse
187
+ include Gibbler::Complex
188
+ attr_accessor :roles
189
+ end
190
+
191
+ a = FullHouse.new
192
+ a.gibbler # => 4192d4cb59975813f117a51dcd4454ac16df6703
193
+
194
+ a.roles = [:jesse, :joey, :danny, :kimmy, :michelle, :dj, :stephanie]
195
+ a.gibbler # => 6ea546919dc4caa2bab69799b71d48810a1b48fa
196
+ ```
197
+
198
+ `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.
199
+
200
+ If you want to support all Ruby objects, add the following to your application:
201
+
202
+ ```ruby
203
+ class Object
204
+ include Gibbler::String
205
+ end
206
+ ```
207
+
208
+ `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).
209
+
210
+ As of 0.7 all Proc objects have the same digest: `12075835e94be34438376cd7a54c8db7e746f15d`.
211
+
212
+ ### Some things to keep in mind
213
+
214
+ * Digest calculation may change between minor releases (as it did between 0.6 and 0.7)
215
+ * 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.
216
+ * Don't forget to enjoy your life!
217
+
218
+ ## What People Are Saying
219
+
220
+ * "nice approach - everything is an object, every object is 'gittish'" -- [@olgen_morten](https://twitter.com/olgen_morten/statuses/2629909133)
221
+ * "gibbler is just awesome" -- [@TomK32](https://twitter.com/TomK32/statuses/2618542872)
222
+ * "wie cool ist Gibbler eigentlich?" -- [@we5](https://twitter.com/we5/statuses/2615274279)
223
+ * "it's nice idea and implementation!" --[HristoHristov](https://www.rubyinside.com/gibbler-git-like-hashes-and-history-for-ruby-objects-1980.html#comment-39092)
224
+
225
+ ## Development
226
+
227
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
228
+
229
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
230
+
231
+ ### Contributing
232
+
233
+ Bug reports and pull requests are welcome [GitHub Issues](https://github.com/delano/gibbler/issues). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/delano/gibbler/blob/main/CODE_OF_CONDUCT.md).
234
+
235
+ ### Thanks
236
+
237
+ * Kalin Harvey ([krrh](https://github.com/kalin)) for the early feedback and artistic direction.
238
+ * Alex Peuchert ([aaalex](https://github.com/aaalex)) for creating the screencast.
239
+
240
+ ## Code of Conduct
241
+
242
+ Everyone interacting in the Gibbler project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/delano/gibbler/blob/main/CODE_OF_CONDUCT.md).
243
+
244
+ ## Credits
245
+
246
+ Gibbler was created by [Delano Mandelbaum](https://delanotes.com/).
247
+
248
+ ## License
249
+
250
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :MAJOR: 0
3
- :MINOR: 8
4
- :PATCH: 10
3
+ :MINOR: 9
4
+ :PATCH: 0
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "gibbler"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/img/whoababy.gif ADDED
Binary file
@@ -1,7 +1,7 @@
1
1
 
2
- require 'gibbler'
2
+ require 'gibbler/mixins'
3
3
 
4
- module Gibbler
4
+ class Gibbler < String
5
5
 
6
6
  module Object
7
7
  alias :digest :gibbler
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
3
 
4
- module Gibbler
4
+ class Gibbler < String
5
5
  class NoRevert < Gibbler::Error
6
6
  def message; "Revert not implemented for #{@obj}" end
7
7
  end
@@ -11,17 +11,17 @@ module Gibbler
11
11
  class BadDigest < Gibbler::Error
12
12
  def message; "Unknown digest: #{@obj}"; end
13
13
  end
14
-
14
+
15
15
  module History
16
16
  extend Attic
17
-
17
+
18
18
  attic :__gibbler_history
19
-
19
+
20
20
  @@mutex = Mutex.new
21
-
21
+
22
22
  def self.mutex; @@mutex; end
23
-
24
- # Returns an Array of digests in the order they were committed.
23
+
24
+ # Returns an Array of digests in the order they were committed.
25
25
  # If +short+ is anything but false, the digests will be converted
26
26
  # to the short 8 character digests.
27
27
  def gibbler_history(short=false)
@@ -37,104 +37,104 @@ module Gibbler
37
37
  self.__gibbler_history[:history].collect { |g| g.short }
38
38
  end
39
39
  end
40
-
40
+
41
41
  # Returns the object stored under the given digest +g+.
42
- # If +g+ is not a valid digest, returns nil.
43
- def gibbler_object(g=nil)
42
+ # If +g+ is not a valid digest, returns nil.
43
+ def gibbler_object(g=nil)
44
44
  g = gibbler_find_long g
45
45
  g = self.gibbler_history.last if g.nil?
46
46
 
47
47
  return unless gibbler_valid? g
48
48
  self.__gibbler_history[:objects][ g ]
49
49
  end
50
-
51
- # Returns the timestamp (a Time object) when the digest +g+ was committed.
52
- # If +g+ is not a valid gibble, returns nil.
50
+
51
+ # Returns the timestamp (a Time object) when the digest +g+ was committed.
52
+ # If +g+ is not a valid gibble, returns nil.
53
53
  def gibbler_stamp(g=nil)
54
54
  g = gibbler_find_long g
55
55
  g = self.gibbler_history.last if g.nil?
56
56
  return unless gibbler_valid? g
57
57
  self.__gibbler_history[:stamp][ g ]
58
58
  end
59
-
59
+
60
60
  # Stores a clone of the current object instance using the current
61
61
  # digest value. If the object was not changed, this method does
62
- # nothing but return the gibble.
62
+ # nothing but return the gibble.
63
63
  #
64
64
  # NOTE: This method is not fully thread safe. It uses a Mutex.synchronize
65
- # but there's a race condition where two threads can attempt to commit at
66
- # near the same time. The first will get the lock and create the commit.
67
- # The second will get the lock and create another commit immediately
68
- # after. What we probably want is for the second thread to return the
65
+ # but there's a race condition where two threads can attempt to commit at
66
+ # near the same time. The first will get the lock and create the commit.
67
+ # The second will get the lock and create another commit immediately
68
+ # after. What we probably want is for the second thread to return the
69
69
  # digest for that first snapshot, but how do we know this was a result
70
70
  # of the race conditioon rather than two legitimate calls for a snapshot?
71
71
  def gibbler_commit
72
72
  now, digest, point = nil,nil,nil
73
-
73
+
74
74
  if self.__gibbler_history.nil?
75
75
  @@mutex.synchronize {
76
76
  self.__gibbler_history ||= { :history => [], :objects => {}, :stamp => {} }
77
77
  }
78
78
  end
79
-
79
+
80
80
  @@mutex.synchronize {
81
81
  now, digest, point = ::Time.now, self.gibbler, self.clone
82
82
  self.__gibbler_history[:history] << digest
83
83
  self.__gibbler_history[:stamp][digest] = now
84
84
  self.__gibbler_history[:objects][digest] = point
85
85
  }
86
-
86
+
87
87
  digest
88
88
  end
89
-
89
+
90
90
  # Revert this object to a previously known state. If called without arguments
91
91
  # it will revert to the most recent commit. If a digest is specified +g+, it
92
- # will revert to that point.
92
+ # will revert to that point.
93
93
  #
94
- # Ruby does not support replacing self (<tt>self = previous_self</tt>) so each
94
+ # Ruby does not support replacing self (`self = previous_self`) so each
95
95
  # object type needs to implement its own __gibbler_revert! method. This default
96
- # run some common checks and then defers to self.__gibbler_revert!.
97
- #
96
+ # run some common checks and then defers to self.__gibbler_revert!.
97
+ #
98
98
  # Raise the following exceptions:
99
99
  # * NoRevert: if this object doesn't have a __gibbler_revert! method
100
100
  # * NoHistory: This object has no commits
101
101
  # * BadDigest: The given digest is not in the history for this object
102
102
  #
103
- # If +g+ matches the current digest value this method does nothing.
103
+ # If +g+ matches the current digest value this method does nothing.
104
104
  #
105
- # Returns the new digest (+g+).
105
+ # Returns the new digest (+g+).
106
106
  def gibbler_revert!(g=nil)
107
107
  raise NoRevert unless self.respond_to? :__gibbler_revert!
108
108
  raise NoHistory, self.class unless gibbler_history?
109
109
  raise BadDigest, g if !g.nil? && !gibbler_valid?(g)
110
-
110
+
111
111
  g = self.gibbler_history.last if g.nil?
112
- g = gibbler_find_long g
113
-
114
- # Do nothing if the given digest matches the current gibble.
112
+ g = gibbler_find_long g
113
+
114
+ # Do nothing if the given digest matches the current gibble.
115
115
  # NOTE: We use __gibbler b/c it doesn't update self.gibbler_cache.
116
116
  unless self.__gibbler == g
117
117
  @@mutex.synchronize {
118
- # Always make sure self.gibbler_digest is a Gibbler::Digest
118
+ # Always make sure self.gibbler_digest is a Gibbler::Digest
119
119
  self.gibbler_cache = g.is_a?(Gibbler::Digest) ? g : Gibbler::Digest.new(g)
120
120
  self.__gibbler_revert!
121
121
  }
122
122
  end
123
-
123
+
124
124
  self.gibbler_cache
125
125
  end
126
-
126
+
127
127
  # Is the given digest +g+ contained in the history for this object?
128
128
  def gibbler_valid?(g)
129
129
  return false unless gibbler_history?
130
130
  gibbler_history.member? gibbler_find_long(g)
131
131
  end
132
-
132
+
133
133
  # Does the current object have any history?
134
134
  def gibbler_history?
135
135
  !gibbler_history.empty?
136
136
  end
137
-
137
+
138
138
  # Returns the long digest associated to the short digest +g+.
139
139
  # If g is longer than 8 characters it returns the value of +g+.
140
140
  def gibbler_find_long(g)
@@ -143,7 +143,7 @@ module Gibbler
143
143
  gibbler_history.select { |d| d.match /\A#{g}/ }.first
144
144
  end
145
145
  end
146
-
146
+
147
147
  end
148
148
 
149
149
  class Hash
@@ -161,7 +161,7 @@ class Array
161
161
  self.push *(self.gibbler_object self.gibbler_cache)
162
162
  end
163
163
  end
164
-
164
+
165
165
  class String
166
166
  include Gibbler::History
167
167
  def __gibbler_revert!
@@ -169,5 +169,3 @@ class String
169
169
  self << (self.gibbler_object self.gibbler_cache)
170
170
  end
171
171
  end
172
-
173
-
@@ -1,9 +1,33 @@
1
+ # rubocop:disable all
2
+ require 'gibbler'
1
3
 
4
+ class NilClass; include Gibbler::Nil; end
5
+ class String; include Gibbler::String; end
6
+ class Symbol; include Gibbler::String; end
7
+ class Integer; include Gibbler::String; end
8
+ class TrueClass; include Gibbler::String; end
9
+ class FalseClass; include Gibbler::String; end
10
+ class Class; include Gibbler::Object; end
11
+ class Module; include Gibbler::Object; end
12
+ class Proc; include Gibbler::Object; end
13
+ class Regexp; include Gibbler::String; end
14
+ class Float; include Gibbler::String; end
15
+ class Date; include Gibbler::String; end
16
+ class Hash; include Gibbler::Hash; end
17
+ class Array; include Gibbler::Array; end
18
+ class Time; include Gibbler::Time; end
19
+ class DateTime < Date; include Gibbler::DateTime; end
20
+ class Range; include Gibbler::Range; end
21
+ class File; include Gibbler::File; end
22
+ class TempFile; include Gibbler::File; end
23
+ class MatchData; include Gibbler::String; end
24
+ class OpenStruct; include Gibbler::Object; end
2
25
 
3
- class String
4
- unless method_defined? :clear
5
- def clear
6
- replace ""
7
- end
8
- end
9
- end
26
+ # URI::Generic must be included towards the
27
+ # end b/c it runs Object#freeze statically.
28
+ module URI; class Generic; include Gibbler::String; end; end
29
+
30
+ # Bundler calls freeze on an instance of Gem::Platform
31
+ module Gem; class Platform; include Gibbler::Complex; end; end
32
+
33
+ module Addressable; class URI; include Gibbler::String; end; end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gibbler
4
+
5
+ module VERSION
6
+ def self.to_s
7
+ load_config
8
+ [@version[:MAJOR], @version[:MINOR], @version[:PATCH]].join('.')
9
+ end
10
+ alias_method :inspect, :to_s
11
+ def self.load_config
12
+ require 'yaml'
13
+ @version ||= YAML.load_file(::File.join(GIBBLER_LIB_HOME, '..', 'VERSION.yml'))
14
+ end
15
+ end
16
+
17
+ end