gibbler 0.8.10 → 0.10.0.pre.RC1

Sign up to get free protection for your applications and to get access to all the features.
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